mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-08 14:04:40 -04:00
Added support for partial payments
This commit is contained in:
parent
be327fbcbf
commit
caae008403
@ -12,6 +12,7 @@ use Validator;
|
|||||||
use View;
|
use View;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
use Cache;
|
use Cache;
|
||||||
|
use Response;
|
||||||
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\Activity;
|
use App\Models\Activity;
|
||||||
|
@ -70,6 +70,8 @@ class AccountGatewayController extends BaseController
|
|||||||
$data['title'] = trans('texts.edit_gateway') . ' - ' . $accountGateway->gateway->name;
|
$data['title'] = trans('texts.edit_gateway') . ' - ' . $accountGateway->gateway->name;
|
||||||
$data['config'] = $configFields;
|
$data['config'] = $configFields;
|
||||||
$data['paymentTypeId'] = $accountGateway->getPaymentType();
|
$data['paymentTypeId'] = $accountGateway->getPaymentType();
|
||||||
|
$data['selectGateways'] = Gateway::where('id', '=', $accountGateway->gateway_id)->get();
|
||||||
|
|
||||||
|
|
||||||
return View::make('accounts.account_gateway', $data);
|
return View::make('accounts.account_gateway', $data);
|
||||||
}
|
}
|
||||||
@ -94,6 +96,7 @@ class AccountGatewayController extends BaseController
|
|||||||
$data['url'] = 'gateways';
|
$data['url'] = 'gateways';
|
||||||
$data['method'] = 'POST';
|
$data['method'] = 'POST';
|
||||||
$data['title'] = trans('texts.add_gateway');
|
$data['title'] = trans('texts.add_gateway');
|
||||||
|
$data['selectGateways'] = Gateway::where('payment_library_id', '=', 1)->where('id', '!=', GATEWAY_PAYPAL_EXPRESS)->where('id', '!=', GATEWAY_PAYPAL_EXPRESS)->orderBy('name')->get();
|
||||||
|
|
||||||
return View::make('accounts.account_gateway', $data);
|
return View::make('accounts.account_gateway', $data);
|
||||||
}
|
}
|
||||||
@ -127,7 +130,6 @@ class AccountGatewayController extends BaseController
|
|||||||
$account->load('account_gateways');
|
$account->load('account_gateways');
|
||||||
$currentGateways = $account->account_gateways;
|
$currentGateways = $account->account_gateways;
|
||||||
$gateways = Gateway::where('payment_library_id', '=', 1)->orderBy('name')->get();
|
$gateways = Gateway::where('payment_library_id', '=', 1)->orderBy('name')->get();
|
||||||
$selectGateways = Gateway::where('payment_library_id', '=', 1)->where('id', '!=', GATEWAY_PAYPAL_EXPRESS)->where('id', '!=', GATEWAY_PAYPAL_EXPRESS)->orderBy('name')->get();
|
|
||||||
|
|
||||||
foreach ($gateways as $gateway) {
|
foreach ($gateways as $gateway) {
|
||||||
$gateway->fields = $gateway->getFields();
|
$gateway->fields = $gateway->getFields();
|
||||||
@ -147,7 +149,6 @@ class AccountGatewayController extends BaseController
|
|||||||
'accountGateway' => $accountGateway,
|
'accountGateway' => $accountGateway,
|
||||||
'config' => false,
|
'config' => false,
|
||||||
'gateways' => $gateways,
|
'gateways' => $gateways,
|
||||||
'selectGateways' => $selectGateways,
|
|
||||||
'creditCardTypes' => $creditCards,
|
'creditCardTypes' => $creditCards,
|
||||||
'tokenBillingOptions' => $tokenBillingOptions,
|
'tokenBillingOptions' => $tokenBillingOptions,
|
||||||
'showBreadcrumbs' => false,
|
'showBreadcrumbs' => false,
|
||||||
@ -174,8 +175,15 @@ class AccountGatewayController extends BaseController
|
|||||||
public function save($accountGatewayPublicId = false)
|
public function save($accountGatewayPublicId = false)
|
||||||
{
|
{
|
||||||
$rules = array();
|
$rules = array();
|
||||||
|
$paymentType = Input::get('payment_type_id');
|
||||||
$gatewayId = Input::get('gateway_id');
|
$gatewayId = Input::get('gateway_id');
|
||||||
|
|
||||||
|
if ($paymentType == PAYMENT_TYPE_PAYPAL) {
|
||||||
|
$gatewayId = GATEWAY_PAYPAL_EXPRESS;
|
||||||
|
} elseif ($paymentType == PAYMENT_TYPE_BITCOIN) {
|
||||||
|
$gatewayId = GATEWAY_BITPAY;
|
||||||
|
}
|
||||||
|
|
||||||
if (!$gatewayId) {
|
if (!$gatewayId) {
|
||||||
Session::flash('error', trans('validation.required', ['attribute' => 'gateway']));
|
Session::flash('error', trans('validation.required', ['attribute' => 'gateway']));
|
||||||
return Redirect::to('gateways/create')
|
return Redirect::to('gateways/create')
|
||||||
@ -206,25 +214,28 @@ class AccountGatewayController extends BaseController
|
|||||||
->withInput();
|
->withInput();
|
||||||
} else {
|
} else {
|
||||||
$account = Account::with('account_gateways')->findOrFail(Auth::user()->account_id);
|
$account = Account::with('account_gateways')->findOrFail(Auth::user()->account_id);
|
||||||
|
$oldConfig = null;
|
||||||
|
|
||||||
if ($accountGatewayPublicId) {
|
if ($accountGatewayPublicId) {
|
||||||
$accountGateway = AccountGateway::scope($accountGatewayPublicId)->firstOrFail();
|
$accountGateway = AccountGateway::scope($accountGatewayPublicId)->firstOrFail();
|
||||||
|
$oldConfig = json_decode($accountGateway->config);
|
||||||
} else {
|
} else {
|
||||||
$accountGateway = AccountGateway::createNew();
|
$accountGateway = AccountGateway::createNew();
|
||||||
$accountGateway->gateway_id = $gatewayId;
|
$accountGateway->gateway_id = $gatewayId;
|
||||||
}
|
}
|
||||||
|
|
||||||
$isMasked = false;
|
|
||||||
|
|
||||||
$config = new stdClass();
|
$config = new stdClass();
|
||||||
foreach ($fields as $field => $details) {
|
foreach ($fields as $field => $details) {
|
||||||
$value = trim(Input::get($gateway->id.'_'.$field));
|
$value = trim(Input::get($gateway->id.'_'.$field));
|
||||||
|
// if the new value is masked use the original value
|
||||||
if ($value && $value === str_repeat('*', strlen($value))) {
|
if ($value && $value === str_repeat('*', strlen($value))) {
|
||||||
$isMasked = true;
|
$value = $oldConfig->$field;
|
||||||
|
}
|
||||||
|
if (!$value && ($field == 'testMode' || $field == 'developerMode')) {
|
||||||
|
// do nothing
|
||||||
|
} else {
|
||||||
|
$config->$field = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
$config->$field = $value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$cardCount = 0;
|
$cardCount = 0;
|
||||||
@ -234,19 +245,12 @@ class AccountGatewayController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the values haven't changed don't update the config
|
$accountGateway->accepted_credit_cards = $cardCount;
|
||||||
if ($isMasked && $accountGatewayPublicId) {
|
$accountGateway->config = json_encode($config);
|
||||||
$accountGateway->accepted_credit_cards = $cardCount;
|
|
||||||
|
if ($accountGatewayPublicId) {
|
||||||
$accountGateway->save();
|
$accountGateway->save();
|
||||||
// if there's an existing config for this gateway update it
|
|
||||||
} elseif (!$isMasked && $accountGatewayPublicId && $accountGateway->gateway_id == $gatewayId) {
|
|
||||||
$accountGateway->accepted_credit_cards = $cardCount;
|
|
||||||
$accountGateway->config = json_encode($config);
|
|
||||||
$accountGateway->save();
|
|
||||||
// otherwise, create a new gateway config
|
|
||||||
} else {
|
} else {
|
||||||
$accountGateway->config = json_encode($config);
|
|
||||||
$accountGateway->accepted_credit_cards = $cardCount;
|
|
||||||
$account->account_gateways()->save($accountGateway);
|
$account->account_gateways()->save($accountGateway);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ class AppController extends BaseController
|
|||||||
// Artisan::call('migrate:rollback', array('--force' => true)); // Debug Purposes
|
// Artisan::call('migrate:rollback', array('--force' => true)); // Debug Purposes
|
||||||
Artisan::call('migrate', array('--force' => true));
|
Artisan::call('migrate', array('--force' => true));
|
||||||
Artisan::call('db:seed', array('--force' => true));
|
Artisan::call('db:seed', array('--force' => true));
|
||||||
|
Artisan::call('optimize', array('--force' => true));
|
||||||
|
|
||||||
$firstName = trim(Input::get('first_name'));
|
$firstName = trim(Input::get('first_name'));
|
||||||
$lastName = trim(Input::get('last_name'));
|
$lastName = trim(Input::get('last_name'));
|
||||||
@ -159,6 +160,7 @@ class AppController extends BaseController
|
|||||||
try {
|
try {
|
||||||
Artisan::call('migrate', array('--force' => true));
|
Artisan::call('migrate', array('--force' => true));
|
||||||
Artisan::call('db:seed', array('--force' => true));
|
Artisan::call('db:seed', array('--force' => true));
|
||||||
|
Artisan::call('optimize', array('--force' => true));
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
Response::make($e->getMessage(), 500);
|
Response::make($e->getMessage(), 500);
|
||||||
}
|
}
|
||||||
@ -172,6 +174,7 @@ class AppController extends BaseController
|
|||||||
if (!Utils::isNinja()) {
|
if (!Utils::isNinja()) {
|
||||||
try {
|
try {
|
||||||
Artisan::call('migrate', array('--force' => true));
|
Artisan::call('migrate', array('--force' => true));
|
||||||
|
Artisan::call('optimize', array('--force' => true));
|
||||||
Cache::flush();
|
Cache::flush();
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
Response::make($e->getMessage(), 500);
|
Response::make($e->getMessage(), 500);
|
||||||
|
@ -204,7 +204,8 @@ class ClientController extends BaseController
|
|||||||
private function save($publicId = null)
|
private function save($publicId = null)
|
||||||
{
|
{
|
||||||
$rules = array(
|
$rules = array(
|
||||||
'email' => 'required',
|
'email' => 'email|required_without:first_name',
|
||||||
|
'first_name' => 'required_without:email',
|
||||||
);
|
);
|
||||||
$validator = Validator::make(Input::all(), $rules);
|
$validator = Validator::make(Input::all(), $rules);
|
||||||
|
|
||||||
|
@ -262,7 +262,7 @@ class PaymentController extends BaseController
|
|||||||
$card = new CreditCard($data);
|
$card = new CreditCard($data);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'amount' => $invoice->balance,
|
'amount' => ($invoice->partial ? $invoice->partial : $invoice->balance),
|
||||||
'card' => $card,
|
'card' => $card,
|
||||||
'currency' => $currencyCode,
|
'currency' => $currencyCode,
|
||||||
'returnUrl' => URL::to('complete'),
|
'returnUrl' => URL::to('complete'),
|
||||||
@ -304,7 +304,7 @@ class PaymentController extends BaseController
|
|||||||
$data = [
|
$data = [
|
||||||
'showBreadcrumbs' => false,
|
'showBreadcrumbs' => false,
|
||||||
'url' => 'payment/'.$invitationKey,
|
'url' => 'payment/'.$invitationKey,
|
||||||
'amount' => $invoice->balance,
|
'amount' => ($invoice->partial ? $invoice->partial : $invoice->balance),
|
||||||
'invoiceNumber' => $invoice->invoice_number,
|
'invoiceNumber' => $invoice->invoice_number,
|
||||||
'client' => $client,
|
'client' => $client,
|
||||||
'contact' => $invitation->contact,
|
'contact' => $invitation->contact,
|
||||||
@ -603,12 +603,17 @@ class PaymentController extends BaseController
|
|||||||
$payment->invitation_id = $invitation->id;
|
$payment->invitation_id = $invitation->id;
|
||||||
$payment->account_gateway_id = $accountGateway->id;
|
$payment->account_gateway_id = $accountGateway->id;
|
||||||
$payment->invoice_id = $invoice->id;
|
$payment->invoice_id = $invoice->id;
|
||||||
$payment->amount = $invoice->balance;
|
$payment->amount = $invoice->partial ? $invoice->partial : $invoice->balance;
|
||||||
$payment->client_id = $invoice->client_id;
|
$payment->client_id = $invoice->client_id;
|
||||||
$payment->contact_id = $invitation->contact_id;
|
$payment->contact_id = $invitation->contact_id;
|
||||||
$payment->transaction_reference = $ref;
|
$payment->transaction_reference = $ref;
|
||||||
$payment->payment_date = date_create()->format('Y-m-d');
|
$payment->payment_date = date_create()->format('Y-m-d');
|
||||||
|
|
||||||
|
if ($invoice->partial) {
|
||||||
|
$invoice->partial = 0;
|
||||||
|
$invoice->save();
|
||||||
|
}
|
||||||
|
|
||||||
if ($payerId) {
|
if ($payerId) {
|
||||||
$payment->payer_id = $payerId;
|
$payment->payer_id = $payerId;
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,7 @@ class Invoice extends EntityModel
|
|||||||
'custom_value2',
|
'custom_value2',
|
||||||
'custom_taxes1',
|
'custom_taxes1',
|
||||||
'custom_taxes2',
|
'custom_taxes2',
|
||||||
|
'partial',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->client->setVisible([
|
$this->client->setVisible([
|
||||||
|
@ -34,7 +34,10 @@ class ClientRepository
|
|||||||
public function getErrors($data)
|
public function getErrors($data)
|
||||||
{
|
{
|
||||||
$contact = isset($data['contacts']) ? (array) $data['contacts'][0] : (isset($data['contact']) ? $data['contact'] : []);
|
$contact = isset($data['contacts']) ? (array) $data['contacts'][0] : (isset($data['contact']) ? $data['contact'] : []);
|
||||||
$validator = \Validator::make($contact, ['email' => 'required|email']);
|
$validator = \Validator::make($contact, [
|
||||||
|
'email' => 'email|required_without:first_name',
|
||||||
|
'first_name' => 'required_without:email',
|
||||||
|
]);
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
return $validator->messages();
|
return $validator->messages();
|
||||||
}
|
}
|
||||||
|
@ -182,7 +182,10 @@ class InvoiceRepository
|
|||||||
public function getErrors($input)
|
public function getErrors($input)
|
||||||
{
|
{
|
||||||
$contact = (array) $input->client->contacts[0];
|
$contact = (array) $input->client->contacts[0];
|
||||||
$rules = ['email' => 'required|email'];
|
$rules = [
|
||||||
|
'email' => 'email|required_without:first_name',
|
||||||
|
'first_name' => 'required_without:email',
|
||||||
|
];
|
||||||
$validator = \Validator::make($contact, $rules);
|
$validator = \Validator::make($contact, $rules);
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
@ -238,6 +241,7 @@ class InvoiceRepository
|
|||||||
$invoice->discount = round(Utils::parseFloat($data['discount']), 2);
|
$invoice->discount = round(Utils::parseFloat($data['discount']), 2);
|
||||||
$invoice->is_amount_discount = $data['is_amount_discount'] ? true : false;
|
$invoice->is_amount_discount = $data['is_amount_discount'] ? true : false;
|
||||||
$invoice->invoice_number = trim($data['invoice_number']);
|
$invoice->invoice_number = trim($data['invoice_number']);
|
||||||
|
$invoice->partial = round(Utils::parseFloat($data['partial']), 2);
|
||||||
$invoice->is_recurring = $data['is_recurring'] && !Utils::isDemo() ? true : false;
|
$invoice->is_recurring = $data['is_recurring'] && !Utils::isDemo() ? true : false;
|
||||||
$invoice->invoice_date = isset($data['invoice_date_sql']) ? $data['invoice_date_sql'] : Utils::toSqlDate($data['invoice_date']);
|
$invoice->invoice_date = isset($data['invoice_date_sql']) ? $data['invoice_date_sql'] : Utils::toSqlDate($data['invoice_date']);
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ class AddCompanyVatNumber extends Migration {
|
|||||||
$table->string('vat_number')->nullable();
|
$table->string('vat_number')->nullable();
|
||||||
});
|
});
|
||||||
|
|
||||||
Schema::table('clients', function($table)
|
Schema::table('clients', function($table)
|
||||||
{
|
{
|
||||||
$table->string('vat_number')->nullable();
|
$table->string('vat_number')->nullable();
|
||||||
});
|
});
|
||||||
@ -34,7 +34,8 @@ class AddCompanyVatNumber extends Migration {
|
|||||||
{
|
{
|
||||||
$table->dropColumn('vat_number');
|
$table->dropColumn('vat_number');
|
||||||
});
|
});
|
||||||
Schema::table('clients', function($table)
|
|
||||||
|
Schema::table('clients', function($table)
|
||||||
{
|
{
|
||||||
$table->dropColumn('vat_number');
|
$table->dropColumn('vat_number');
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class AddPartialAmountToInvoices extends Migration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('invoices', function($table)
|
||||||
|
{
|
||||||
|
$table->decimal('partial', 13, 2)->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('invoices', function($table)
|
||||||
|
{
|
||||||
|
$table->dropColumn('partial');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
5
public/css/built.css
vendored
5
public/css/built.css
vendored
@ -2900,6 +2900,7 @@ body.modal-open { overflow:inherit; padding-right:inherit !important; }
|
|||||||
.radio input[type="radio"],
|
.radio input[type="radio"],
|
||||||
.checkbox input[type="checkbox"] {
|
.checkbox input[type="checkbox"] {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
padding-left: 0px !important;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
height: inherit;
|
height: inherit;
|
||||||
width: inherit;
|
width: inherit;
|
||||||
@ -2908,3 +2909,7 @@ body.modal-open { overflow:inherit; padding-right:inherit !important; }
|
|||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.checkbox > label {
|
||||||
|
padding-left: 0px !important;
|
||||||
|
}
|
5
public/css/style.css
vendored
5
public/css/style.css
vendored
@ -792,6 +792,7 @@ body.modal-open { overflow:inherit; padding-right:inherit !important; }
|
|||||||
.radio input[type="radio"],
|
.radio input[type="radio"],
|
||||||
.checkbox input[type="checkbox"] {
|
.checkbox input[type="checkbox"] {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
padding-left: 0px !important;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
height: inherit;
|
height: inherit;
|
||||||
width: inherit;
|
width: inherit;
|
||||||
@ -800,3 +801,7 @@ body.modal-open { overflow:inherit; padding-right:inherit !important; }
|
|||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.checkbox > label {
|
||||||
|
padding-left: 0px !important;
|
||||||
|
}
|
@ -32490,7 +32490,11 @@ function calculateAmounts(invoice) {
|
|||||||
total += roundToTwo(invoice.custom_value2);
|
total += roundToTwo(invoice.custom_value2);
|
||||||
}
|
}
|
||||||
|
|
||||||
invoice.balance_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance));
|
if (NINJA.parseFloat(invoice.partial)) {
|
||||||
|
invoice.balance_amount = roundToTwo(invoice.partial);
|
||||||
|
} else {
|
||||||
|
invoice.balance_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance));
|
||||||
|
}
|
||||||
invoice.discount_amount = discount;
|
invoice.discount_amount = discount;
|
||||||
invoice.tax_amount = tax;
|
invoice.tax_amount = tax;
|
||||||
invoice.has_taxes = hasTaxes;
|
invoice.has_taxes = hasTaxes;
|
||||||
|
@ -967,7 +967,11 @@ function calculateAmounts(invoice) {
|
|||||||
total += roundToTwo(invoice.custom_value2);
|
total += roundToTwo(invoice.custom_value2);
|
||||||
}
|
}
|
||||||
|
|
||||||
invoice.balance_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance));
|
if (NINJA.parseFloat(invoice.partial)) {
|
||||||
|
invoice.balance_amount = roundToTwo(invoice.partial);
|
||||||
|
} else {
|
||||||
|
invoice.balance_amount = roundToTwo(total) - (roundToTwo(invoice.amount) - roundToTwo(invoice.balance));
|
||||||
|
}
|
||||||
invoice.discount_amount = discount;
|
invoice.discount_amount = discount;
|
||||||
invoice.tax_amount = tax;
|
invoice.tax_amount = tax;
|
||||||
invoice.has_taxes = hasTaxes;
|
invoice.has_taxes = hasTaxes;
|
||||||
|
@ -589,5 +589,7 @@ return array(
|
|||||||
'payment_type_credit_card' => 'Credit card',
|
'payment_type_credit_card' => 'Credit card',
|
||||||
'payment_type_paypal' => 'PayPal',
|
'payment_type_paypal' => 'PayPal',
|
||||||
'payment_type_bitcoin' => 'Bitcoin',
|
'payment_type_bitcoin' => 'Bitcoin',
|
||||||
|
'knowledge_base' => 'Knowledge Base',
|
||||||
|
'partial' => 'Partial',
|
||||||
|
|
||||||
);
|
);
|
||||||
|
@ -21,6 +21,8 @@
|
|||||||
@endif
|
@endif
|
||||||
@endforeach
|
@endforeach
|
||||||
@endif
|
@endif
|
||||||
|
@else
|
||||||
|
{!! Former::populateField('gateway_id', GATEWAY_AUTHORIZE_NET) !!}
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
{!! Former::select('payment_type_id')
|
{!! Former::select('payment_type_id')
|
||||||
@ -28,7 +30,7 @@
|
|||||||
->addGroupClass('payment-type-option')
|
->addGroupClass('payment-type-option')
|
||||||
->onchange('setPaymentType()') !!}
|
->onchange('setPaymentType()') !!}
|
||||||
|
|
||||||
{!! Former::select('gateway_id')->addOption('', '')
|
{!! Former::select('gateway_id')
|
||||||
->dataClass('gateway-dropdown')
|
->dataClass('gateway-dropdown')
|
||||||
->addGroupClass('gateway-option')
|
->addGroupClass('gateway-option')
|
||||||
->fromQuery($selectGateways, 'name', 'id')
|
->fromQuery($selectGateways, 'name', 'id')
|
||||||
@ -42,7 +44,7 @@
|
|||||||
@if (in_array($field, ['solutionType', 'landingPage', 'headerImageUrl', 'brandName']))
|
@if (in_array($field, ['solutionType', 'landingPage', 'headerImageUrl', 'brandName']))
|
||||||
{{-- do nothing --}}
|
{{-- do nothing --}}
|
||||||
@elseif ($field == 'testMode' || $field == 'developerMode')
|
@elseif ($field == 'testMode' || $field == 'developerMode')
|
||||||
{{-- Former::checkbox($gateway->id.'_'.$field)->label(Utils::toSpaceCase($field))->text('Enable') --}}
|
{!! Former::checkbox($gateway->id.'_'.$field)->label(Utils::toSpaceCase($field))->text('Enable')->value('true') !!}
|
||||||
@elseif ($field == 'username' || $field == 'password')
|
@elseif ($field == 'username' || $field == 'password')
|
||||||
{!! Former::text($gateway->id.'_'.$field)->label('API '. ucfirst(Utils::toSpaceCase($field))) !!}
|
{!! Former::text($gateway->id.'_'.$field)->label('API '. ucfirst(Utils::toSpaceCase($field))) !!}
|
||||||
@else
|
@else
|
||||||
|
@ -72,6 +72,7 @@
|
|||||||
|
|
||||||
<p class="link">
|
<p class="link">
|
||||||
{!! link_to('/forgot', trans('texts.forgot_password')) !!}
|
{!! link_to('/forgot', trans('texts.forgot_password')) !!}
|
||||||
|
{!! link_to(NINJA_WEB_URL.'/knowledgebase/', trans('texts.knowledge_base'), ['target' => '_blank', 'class' => 'pull-right']) !!}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
|
|
||||||
{!! Former::legend('Organization') !!}
|
{!! Former::legend('Organization') !!}
|
||||||
{!! Former::text('name') !!}
|
{!! Former::text('name') !!}
|
||||||
{!! Former::text('id_number') !!}
|
{!! Former::text('id_number') !!}
|
||||||
{!! Former::text('vat_number') !!}
|
{!! Former::text('vat_number') !!}
|
||||||
{!! Former::text('work_phone')->label('Phone') !!}
|
{!! Former::text('work_phone')->label('Phone') !!}
|
||||||
{!! Former::textarea('notes') !!}
|
{!! Former::textarea('notes') !!}
|
||||||
|
|
||||||
|
@ -7,11 +7,8 @@
|
|||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!--<h3>{{ $title }} Client</h3>-->
|
|
||||||
|
|
||||||
{!! Former::open($url)->addClass('col-md-12 warn-on-exit')->method($method)->rules(array(
|
{!! Former::open($url)->addClass('col-md-12 warn-on-exit')->method($method) !!}
|
||||||
'email' => 'email|required'
|
|
||||||
)); !!}
|
|
||||||
|
|
||||||
@if ($client)
|
@if ($client)
|
||||||
{!! Former::populate($client) !!}
|
{!! Former::populate($client) !!}
|
||||||
|
@ -5,6 +5,15 @@
|
|||||||
|
|
||||||
<script src="{{ asset('js/pdf_viewer.js') }}" type="text/javascript"></script>
|
<script src="{{ asset('js/pdf_viewer.js') }}" type="text/javascript"></script>
|
||||||
<script src="{{ asset('js/compatibility.js') }}" type="text/javascript"></script>
|
<script src="{{ asset('js/compatibility.js') }}" type="text/javascript"></script>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
.partial div.checkbox {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
.partial span.input-group-addon {
|
||||||
|
padding-right: 30px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@stop
|
@stop
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
@ -18,7 +27,6 @@
|
|||||||
|
|
||||||
{!! Former::open($url)->method($method)->addClass('warn-on-exit')->rules(array(
|
{!! Former::open($url)->method($method)->addClass('warn-on-exit')->rules(array(
|
||||||
'client' => 'required',
|
'client' => 'required',
|
||||||
'email' => 'required',
|
|
||||||
'product_key' => 'max:20'
|
'product_key' => 'max:20'
|
||||||
)) !!}
|
)) !!}
|
||||||
|
|
||||||
@ -51,7 +59,7 @@
|
|||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div data-bind="with: client">
|
<div data-bind="with: client">
|
||||||
<div style="display:none" class="form-group" data-bind="visible: contacts().length > 0 && contacts()[0].email(), foreach: contacts">
|
<div style="display:none" class="form-group" data-bind="visible: contacts().length > 0 && (contacts()[0].email() || contacts()[0].first_name()), foreach: contacts">
|
||||||
<div class="col-lg-8 col-lg-offset-4">
|
<div class="col-lg-8 col-lg-offset-4">
|
||||||
<label class="checkbox" data-bind="attr: {for: $index() + '_check'}" onclick="refreshPDF()">
|
<label class="checkbox" data-bind="attr: {for: $index() + '_check'}" onclick="refreshPDF()">
|
||||||
<input type="checkbox" value="1" data-bind="checked: send_invoice, attr: {id: $index() + '_check'}">
|
<input type="checkbox" value="1" data-bind="checked: send_invoice, attr: {id: $index() + '_check'}">
|
||||||
@ -68,6 +76,10 @@
|
|||||||
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'invoice_date\')"></i>') !!}
|
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'invoice_date\')"></i>') !!}
|
||||||
{!! Former::text('due_date')->data_bind("datePicker: due_date, valueUpdate: 'afterkeydown'")
|
{!! Former::text('due_date')->data_bind("datePicker: due_date, valueUpdate: 'afterkeydown'")
|
||||||
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'due_date\')"></i>') !!}
|
->data_date_format(Session::get(SESSION_DATE_PICKER_FORMAT, DEFAULT_DATE_PICKER_FORMAT))->append('<i class="glyphicon glyphicon-calendar" onclick="toggleDatePicker(\'due_date\')"></i>') !!}
|
||||||
|
|
||||||
|
{!! Former::text('partial')->data_bind("value: partial, valueUpdate: 'afterkeydown', enable: is_partial")
|
||||||
|
->addGroupClass('partial')->append(Former::checkbox('is_partial')->raw()
|
||||||
|
->data_bind('checked: is_partial')->onclick('onPartialEnabled()') . ' ' . (trans('texts.enable'))) !!}
|
||||||
</div>
|
</div>
|
||||||
@if ($entityType == ENTITY_INVOICE)
|
@if ($entityType == ENTITY_INVOICE)
|
||||||
<div data-bind="visible: is_recurring" style="display: none">
|
<div data-bind="visible: is_recurring" style="display: none">
|
||||||
@ -83,7 +95,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div data-bind="visible: invoice_status_id() === 0">
|
<div data-bind="visible: invoice_status_id() === 0">
|
||||||
{!! Former::checkbox('recurring')->onclick('setEmailEnabled()')->text(trans('texts.enable').' <a href="#" onclick="showLearnMore()"><i class="glyphicon glyphicon-question-sign"></i> '.trans('texts.learn_more').'</a>')->data_bind("checked: is_recurring")
|
{!! Former::checkbox('recurring')->onclick('onRecurringEnabled()')->text(trans('texts.enable').' <a href="#" onclick="showLearnMore()"><i class="glyphicon glyphicon-question-sign"></i> '.trans('texts.learn_more').'</a>')->data_bind("checked: is_recurring")
|
||||||
->inlineHelp($invoice && $invoice->last_sent_date ? 'Last invoice sent ' . Utils::dateToString($invoice->last_sent_date) : '') !!}
|
->inlineHelp($invoice && $invoice->last_sent_date ? 'Last invoice sent ' . Utils::dateToString($invoice->last_sent_date) : '') !!}
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@ -265,7 +277,7 @@
|
|||||||
<td class="hide-border" colspan="3"/>
|
<td class="hide-border" colspan="3"/>
|
||||||
<td style="display:none" data-bind="visible: $root.invoice_item_taxes.show"/>
|
<td style="display:none" data-bind="visible: $root.invoice_item_taxes.show"/>
|
||||||
<td colspan="{{ $account->hide_quantity ? 1 : 2 }}"><b>{{ trans($entityType == ENTITY_INVOICE ? 'texts.balance_due' : 'texts.total') }}</b></td>
|
<td colspan="{{ $account->hide_quantity ? 1 : 2 }}"><b>{{ trans($entityType == ENTITY_INVOICE ? 'texts.balance_due' : 'texts.total') }}</b></td>
|
||||||
<td style="text-align: right"><span data-bind="text: totals.total"/></td>
|
<td style="text-align: right"><span data-bind="text: totals.total"></span></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
</tfoot>
|
</tfoot>
|
||||||
@ -588,7 +600,7 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#invoice_footer, #terms, #public_notes, #invoice_number, #invoice_date, #due_date, #po_number, #discount, #currency_id, #invoice_design_id, #recurring, #is_amount_discount').change(function() {
|
$('#invoice_footer, #terms, #public_notes, #invoice_number, #invoice_date, #due_date, #po_number, #discount, #currency_id, #invoice_design_id, #recurring, #is_amount_discount, #partial').change(function() {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
refreshPDF();
|
refreshPDF();
|
||||||
}, 1);
|
}, 1);
|
||||||
@ -752,7 +764,7 @@
|
|||||||
var isValid = false;
|
var isValid = false;
|
||||||
for (var i=0; i<model.invoice().client().contacts().length; i++) {
|
for (var i=0; i<model.invoice().client().contacts().length; i++) {
|
||||||
var contact = model.invoice().client().contacts()[i];
|
var contact = model.invoice().client().contacts()[i];
|
||||||
if (isValidEmailAddress(contact.email())) {
|
if (isValidEmailAddress(contact.email()) || contact.first_name()) {
|
||||||
isValid = true;
|
isValid = true;
|
||||||
} else {
|
} else {
|
||||||
isValid = false;
|
isValid = false;
|
||||||
@ -984,10 +996,23 @@
|
|||||||
self.clientFormComplete = function() {
|
self.clientFormComplete = function() {
|
||||||
trackUrl('/save_client_form');
|
trackUrl('/save_client_form');
|
||||||
|
|
||||||
|
var email = $('#email0').val();
|
||||||
|
var firstName = $('#first_name').val();
|
||||||
|
var lastName = $('#last_name').val();
|
||||||
|
var name = $('#name').val();
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
//
|
||||||
|
} else if (firstName || lastName) {
|
||||||
|
name = firstName + ' ' + lastName;
|
||||||
|
} else {
|
||||||
|
name = email;
|
||||||
|
}
|
||||||
|
|
||||||
var isValid = true;
|
var isValid = true;
|
||||||
$("input[name='email']").each(function(item, value) {
|
$("input[name='email']").each(function(item, value) {
|
||||||
var email = $(value).val();
|
var email = $(value).val();
|
||||||
if (!email || !isValidEmailAddress(email)) {
|
if (!name && (!email || !isValidEmailAddress(email))) {
|
||||||
isValid = false;
|
isValid = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -996,25 +1021,12 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var email = $('#email0').val();
|
|
||||||
var firstName = $('#first_name').val();
|
|
||||||
var lastName = $('#last_name').val();
|
|
||||||
var name = $('#name').val();
|
|
||||||
|
|
||||||
if (self.invoice().client().public_id() == 0) {
|
if (self.invoice().client().public_id() == 0) {
|
||||||
self.invoice().client().public_id(-1);
|
self.invoice().client().public_id(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
model.setDueDate();
|
model.setDueDate();
|
||||||
|
|
||||||
if (name) {
|
|
||||||
//
|
|
||||||
} else if (firstName || lastName) {
|
|
||||||
name = firstName + ' ' + lastName;
|
|
||||||
} else {
|
|
||||||
name = email;
|
|
||||||
}
|
|
||||||
|
|
||||||
setComboboxValue($('.client_select'), -1, name);
|
setComboboxValue($('.client_select'), -1, name);
|
||||||
|
|
||||||
//$('.client_select select').combobox('setSelected');
|
//$('.client_select select').combobox('setSelected');
|
||||||
@ -1078,6 +1090,8 @@
|
|||||||
self.amount = ko.observable(0);
|
self.amount = ko.observable(0);
|
||||||
self.balance = ko.observable(0);
|
self.balance = ko.observable(0);
|
||||||
self.invoice_design_id = ko.observable({{ $account->invoice_design_id }});
|
self.invoice_design_id = ko.observable({{ $account->invoice_design_id }});
|
||||||
|
self.partial = ko.observable(0);
|
||||||
|
self.is_partial = ko.observable(false);
|
||||||
|
|
||||||
self.custom_value1 = ko.observable(0);
|
self.custom_value1 = ko.observable(0);
|
||||||
self.custom_value2 = ko.observable(0);
|
self.custom_value2 = ko.observable(0);
|
||||||
@ -1111,7 +1125,7 @@
|
|||||||
applyComboboxListeners();
|
applyComboboxListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
ko.mapping.fromJS(data, self.mapping, self);
|
ko.mapping.fromJS(data, self.mapping, self);
|
||||||
self.is_recurring(parseInt(data.is_recurring));
|
self.is_recurring(parseInt(data.is_recurring));
|
||||||
} else {
|
} else {
|
||||||
@ -1237,27 +1251,20 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.totals.rawPaidToDate = ko.computed(function() {
|
self.totals.rawPaidToDate = ko.computed(function() {
|
||||||
return accounting.toFixed(self.amount(),2) - accounting.toFixed(self.balance(),2);
|
return accounting.toFixed(self.amount(),2) - accounting.toFixed(self.balance(),2);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.totals.paidToDate = ko.computed(function() {
|
self.totals.paidToDate = ko.computed(function() {
|
||||||
var total = self.totals.rawPaidToDate();
|
var total = self.totals.rawPaidToDate();
|
||||||
return formatMoney(total, self.client().currency_id());
|
return formatMoney(total, self.client().currency_id());
|
||||||
});
|
});
|
||||||
|
|
||||||
this.totals.total = ko.computed(function() {
|
self.totals.rawTotal = ko.computed(function() {
|
||||||
var total = accounting.toFixed(self.totals.rawSubtotal(),2);
|
var total = accounting.toFixed(self.totals.rawSubtotal(),2);
|
||||||
var discount = self.totals.rawDiscounted();
|
var discount = self.totals.rawDiscounted();
|
||||||
total -= discount;
|
total -= discount;
|
||||||
|
|
||||||
/*
|
|
||||||
var discount = parseFloat(self.discount());
|
|
||||||
if (discount > 0) {
|
|
||||||
total = roundToTwo(total * ((100 - discount)/100));
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
var customValue1 = roundToTwo(self.custom_value1());
|
var customValue1 = roundToTwo(self.custom_value1());
|
||||||
var customValue2 = roundToTwo(self.custom_value2());
|
var customValue2 = roundToTwo(self.custom_value2());
|
||||||
var customTaxes1 = self.custom_taxes1() == 1;
|
var customTaxes1 = self.custom_taxes1() == 1;
|
||||||
@ -1287,9 +1294,13 @@
|
|||||||
total -= paid;
|
total -= paid;
|
||||||
}
|
}
|
||||||
|
|
||||||
return formatMoney(total, self.client().currency_id());
|
return total;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.totals.total = ko.computed(function() {
|
||||||
|
return formatMoney(self.is_partial() ? self.partial() : self.totals.rawTotal(), self.client().currency_id());
|
||||||
|
});
|
||||||
|
|
||||||
self.onDragged = function(item) {
|
self.onDragged = function(item) {
|
||||||
refreshPDF();
|
refreshPDF();
|
||||||
}
|
}
|
||||||
@ -1388,11 +1399,13 @@
|
|||||||
if (self.first_name() || self.last_name()) {
|
if (self.first_name() || self.last_name()) {
|
||||||
str += self.first_name() + ' ' + self.last_name() + '<br/>';
|
str += self.first_name() + ' ' + self.last_name() + '<br/>';
|
||||||
}
|
}
|
||||||
str += self.email();
|
if (self.email()) {
|
||||||
|
str += self.email() + '<br/>';
|
||||||
|
}
|
||||||
|
|
||||||
@if (Utils::isConfirmed())
|
@if (Utils::isConfirmed())
|
||||||
if (self.invitation_link()) {
|
if (self.invitation_link()) {
|
||||||
str += '<br/><a href="' + self.invitation_link() + '" target="_blank">{{ trans('texts.view_as_recipient') }}</a>';
|
str += '<a href="' + self.invitation_link() + '" target="_blank">{{ trans('texts.view_as_recipient') }}</a>';
|
||||||
}
|
}
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@ -1593,10 +1606,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setEmailEnabled()
|
function onPartialEnabled()
|
||||||
|
{
|
||||||
|
if ($('#is_partial').prop('checked')) {
|
||||||
|
model.invoice().partial(model.invoice().totals.rawTotal() || '');
|
||||||
|
} else {
|
||||||
|
model.invoice().partial('');
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshPDF();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRecurringEnabled()
|
||||||
{
|
{
|
||||||
if ($('#recurring').prop('checked')) {
|
if ($('#recurring').prop('checked')) {
|
||||||
$('#email_button').attr('disabled', true);
|
$('#email_button').attr('disabled', true);
|
||||||
|
model.invoice().partial('');
|
||||||
} else {
|
} else {
|
||||||
$('#email_button').removeAttr('disabled');
|
$('#email_button').removeAttr('disabled');
|
||||||
}
|
}
|
||||||
@ -1622,12 +1647,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@if ($data)
|
@if ($data)
|
||||||
window.model = new ViewModel({{ $data }});
|
window.model = new ViewModel({{ $data }});
|
||||||
@else
|
@else
|
||||||
window.model = new ViewModel();
|
window.model = new ViewModel();
|
||||||
model.addTaxRate();
|
model.addTaxRate();
|
||||||
@foreach ($taxRates as $taxRate)
|
@foreach ($taxRates as $taxRate)
|
||||||
model.addTaxRate({{ $taxRate }});
|
model.addTaxRate({!! $taxRate !!});
|
||||||
@endforeach
|
@endforeach
|
||||||
@if ($invoice)
|
@if ($invoice)
|
||||||
var invoice = {!! $invoice !!};
|
var invoice = {!! $invoice !!};
|
||||||
@ -1635,7 +1660,10 @@
|
|||||||
if (model.invoice().is_recurring() === '0') {
|
if (model.invoice().is_recurring() === '0') {
|
||||||
model.invoice().is_recurring(false);
|
model.invoice().is_recurring(false);
|
||||||
}
|
}
|
||||||
var invitationContactIds = {!! json_encode($invitationContactIds) !!};
|
if (NINJA.parseFloat(model.invoice().partial())) {
|
||||||
|
model.invoice().is_partial(true);
|
||||||
|
}
|
||||||
|
var invitationContactIds = {!! json_encode($invitationContactIds) !!};
|
||||||
var client = clientMap[invoice.client.public_id];
|
var client = clientMap[invoice.client.public_id];
|
||||||
if (client) { // in case it's deleted
|
if (client) { // in case it's deleted
|
||||||
for (var i=0; i<client.contacts.length; i++) {
|
for (var i=0; i<client.contacts.length; i++) {
|
||||||
@ -1666,6 +1694,7 @@
|
|||||||
|
|
||||||
// display blank instead of '0'
|
// display blank instead of '0'
|
||||||
if (!NINJA.parseFloat(model.invoice().discount())) model.invoice().discount('');
|
if (!NINJA.parseFloat(model.invoice().discount())) model.invoice().discount('');
|
||||||
|
if (!NINJA.parseFloat(model.invoice().partial())) model.invoice().partial('');
|
||||||
if (!model.invoice().custom_value1()) model.invoice().custom_value1('');
|
if (!model.invoice().custom_value1()) model.invoice().custom_value1('');
|
||||||
if (!model.invoice().custom_value2()) model.invoice().custom_value2('');
|
if (!model.invoice().custom_value2()) model.invoice().custom_value2('');
|
||||||
|
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
var invoiceDesigns = {{ $invoiceDesigns }};
|
var invoiceDesigns = {!! $invoiceDesigns !!};
|
||||||
var currentInvoice = {{ $invoice }};
|
var currentInvoice = {!! $invoice !!};
|
||||||
var versionsJson = {{ $versionsJson }};
|
var versionsJson = {!! $versionsJson !!};
|
||||||
|
|
||||||
function getPDFString() {
|
function getPDFString() {
|
||||||
|
|
||||||
@ -49,10 +49,10 @@
|
|||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
{{ Former::open()->addClass('form-inline')->onchange('refreshPDF()') }}
|
{!! Former::open()->addClass('form-inline')->onchange('refreshPDF()') !!}
|
||||||
{{ Former::select('version')->options($versionsSelect)->label(trans('select_version')) }}
|
{!! Former::select('version')->options($versionsSelect)->label(trans('select_version')) !!}
|
||||||
{{ Button::success_link(URL::to($invoice->getEntityType() . 's/' . $invoice->public_id . '/edit'), trans('texts.edit_' . $invoice->getEntityType()), array('class' => 'pull-right')) }}
|
{!! Button::success(trans('texts.edit_' . $invoice->getEntityType()))->asLinkTo('/' . $invoice->getEntityType() . 's/' . $invoice->public_id . '/edit')->withAttributes(array('class' => 'pull-right')) !!}
|
||||||
{{ Former::close() }}
|
{!! Former::close() !!}
|
||||||
|
|
||||||
<br/> <br/>
|
<br/> <br/>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user