1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-16 08:02:40 +01:00

Merge pull request #6138 from turbo124/v5-stable

v5.2.7
This commit is contained in:
David Bomba 2021-06-27 07:05:28 +10:00 committed by GitHub
commit 694a704d89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 110082 additions and 109680 deletions

View File

@ -1 +1 @@
5.2.6
5.2.7

View File

@ -60,36 +60,36 @@ class SubdomainFill extends Command
});
$db1 = Company::on('db-ninja-01')->get();
// $db1 = Company::on('db-ninja-01')->get();
$db1->each(function ($company){
// $db1->each(function ($company){
$db2 = Company::on('db-ninja-02a')->find($company->id);
// $db2 = Company::on('db-ninja-02a')->find($company->id);
if($db2)
{
$db2->subdomain = $company->subdomain;
$db2->save();
}
// if($db2)
// {
// $db2->subdomain = $company->subdomain;
// $db2->save();
// }
});
// });
$db1 = null;
$db2 = null;
// $db1 = null;
// $db2 = null;
$db2 = Company::on('db-ninja-02')->get();
// $db2 = Company::on('db-ninja-02')->get();
$db2->each(function ($company){
// $db2->each(function ($company){
$db1 = Company::on('db-ninja-01a')->find($company->id);
// $db1 = Company::on('db-ninja-01a')->find($company->id);
if($db1)
{
$db1->subdomain = $company->subdomain;
$db1->save();
}
});
// if($db1)
// {
// $db1->subdomain = $company->subdomain;
// $db1->save();
// }
// });
}
}

View File

@ -28,7 +28,7 @@ class CompanySettings extends BaseSettings
public $lock_invoices = 'off'; //off,when_sent,when_paid //@implemented
public $enable_client_portal_tasks = false; //@ben to implement
public $show_all_tasks_client_portal = 'all'; // all, uninvoiced, invoiced
public $show_all_tasks_client_portal = 'invoiced'; // all, uninvoiced, invoiced
public $enable_client_portal_password = false; //@implemented
public $enable_client_portal = true; //@implemented
public $enable_client_portal_dashboard = false; // @TODO There currently is no dashboard so this is pending
@ -398,7 +398,6 @@ class CompanySettings extends BaseSettings
'email_template_reminder2' => 'string',
'email_template_reminder3' => 'string',
'email_template_reminder_endless' => 'string',
'enable_client_portal_password' => 'bool',
'inclusive_taxes' => 'bool',
'invoice_number_pattern' => 'string',
'invoice_number_counter' => 'integer',

View File

@ -77,7 +77,7 @@ class Handler extends ExceptionHandler
return;
}
if(Ninja::isHosted()){
if(Ninja::isHosted() && !($exception instanceof ValidationException)){
app('sentry')->configureScope(function (Scope $scope): void {

View File

@ -46,8 +46,6 @@ class GmailTransport extends Transport
$this->gmail = null;
$this->gmail = new Mail;
nlog($message->getBcc());
/*We should nest the token in the message and then discard it as needed*/
$token = $message->getHeaders()->get('GmailToken')->getValue();
@ -62,13 +60,13 @@ class GmailTransport extends Transport
$this->gmail->message($message->getBody());
$this->gmail->cc($message->getCc());
$this->gmail->bcc($message->getBcc());
if(is_array($message->getBcc()))
$this->gmail->bcc(array_keys($message->getBcc()));
foreach ($message->getChildren() as $child)
{
nlog("trying to attach");
if($child->getContentType() != 'text/plain')
{

View File

@ -201,6 +201,14 @@ class LoginController extends BaseController
->header('X-Api-Version', config('ninja.minimum_client_version'));
}
/* If for some reason we lose state on the default company ie. a company is deleted - always make sure we can default to a company*/
if(!$user->account->default_company){
$account = $user->account;
$account->default_company_id = $user->companies->first()->id;
$account->save();
$user = $user->fresh();
}
$user->setCompany($user->account->default_company);
$this->setLoginCache($user);

View File

@ -21,8 +21,6 @@ class PaymentWebhookController extends Controller
public function __invoke(PaymentWebhookRequest $request, string $company_key, string $company_gateway_id)
{
// MultiDB::findAndSetDbByCompanyKey($company_key);
$payment = $request->getPayment();
if(!$payment)
@ -33,7 +31,6 @@ class PaymentWebhookController extends Controller
if(!$client)
return response()->json(['message' => 'Client record not found.'], 400);
return $request->getCompanyGateway()
->driver($client)
->processWebhookRequest($request, $payment);

View File

@ -155,7 +155,7 @@ class PreviewController extends BaseController
$t = app('translator');
$t->replace(Ninja::transformTranslations(auth()->user()->company()->settings));
DB::beginTransaction();
DB::connection(config('database.default'))->beginTransaction();
$client = Client::factory()->create([
'user_id' => auth()->user()->id,
@ -230,7 +230,7 @@ class PreviewController extends BaseController
$file_path = PreviewPdf::dispatchNow($maker->getCompiledHTML(true), auth()->user()->company());
DB::rollBack();
DB::connection(config('database.default'))->rollBack();
$response = Response::make($file_path, 200);
$response->header('Content-Type', 'application/pdf');

View File

@ -11,11 +11,17 @@
namespace App\Http\Controllers;
use PragmaRX\Google2FA\Google2FA;
use App\Models\User;
use App\Transformers\UserTransformer;
use Crypt;
use PragmaRX\Google2FA\Google2FA;
class TwoFactorController extends BaseController
{
protected $entity_type = User::class;
protected $entity_transformer = UserTransformer::class;
public function setupTwoFactor()
{
$user = auth()->user();

View File

@ -0,0 +1,56 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Livewire\PaymentMethods;
use App\Libraries\MultiDB;
use Livewire\Component;
class UpdateDefaultMethod extends Component
{
/** @var \App\Models\Company */
public $company;
/** @var \App\Models\ClientGatewayToken */
public $token;
/** @var \App\Models\Client */
public $client;
public function mount()
{
$this->company = $this->client->company;
MultiDB::setDb($this->company->db);
$this->is_disabled = $this->token->is_default;
}
public function makeDefault(): void
{
if ($this->token->is_default) {
return;
}
$this->client->gateway_tokens()->update(['is_default' => 0]);
$this->token->is_default = 1;
$this->token->save();
$this->emit('UpdateDefaultMethod::method-updated');
}
public function render()
{
return render('components.livewire.update-default-payment-method');
}
}

View File

@ -0,0 +1,34 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Livewire\RecurringInvoices;
use Livewire\Component;
class UpdateAutoBilling extends Component
{
/** @var \App\Models\RecurringInvoice */
public $invoice;
public function updateAutoBilling(): void
{
if ($this->invoice->auto_bill === 'optin' || $this->invoice->auto_bill === 'optout') {
$this->invoice->auto_bill_enabled = !$this->invoice->auto_bill_enabled;
$this->invoice->save();
}
}
public function render()
{
return render('components.livewire.recurring-invoices-switch-autobilling');
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests\ClientPortal\PaymentMethod;
use App\Http\Requests\Request;
use Illuminate\Foundation\Http\FormRequest;
class VerifyPaymentMethodRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'transactions.*' => 'integer'
];
}
}

View File

@ -45,6 +45,7 @@ class RecurringInvoicesCron
if (! config('ninja.db.multi_db_enabled')) {
$recurring_invoices = RecurringInvoice::where('next_send_date', '<=', now()->toDateTimeString())
->whereNotNull('next_send_date')
->whereNull('deleted_at')
->where('status_id', RecurringInvoice::STATUS_ACTIVE)
->where('remaining_cycles', '!=', '0')
->with('company')
@ -66,6 +67,7 @@ class RecurringInvoicesCron
$recurring_invoices = RecurringInvoice::where('next_send_date', '<=', now()->toDateTimeString())
->whereNotNull('next_send_date')
->whereNull('deleted_at')
->where('status_id', RecurringInvoice::STATUS_ACTIVE)
->where('remaining_cycles', '!=', '0')
->with('company')
@ -74,7 +76,7 @@ class RecurringInvoicesCron
nlog(now()->format('Y-m-d') . ' Sending Recurring Invoices. Count = '.$recurring_invoices->count().' On Database # '.$db);
$recurring_invoices->each(function ($recurring_invoice, $key) {
nlog("Current date = " . now()->format("Y-m-d") . " Recurring date = " .$recurring_invoice->next_send_date);
nlog("Current date = " . now()->format("Y-m-d") . " Recurring date = " .$recurring_invoice->next_send_date ." Recurring #id = ". $recurring_invoice->id);
if (!$recurring_invoice->company->is_disabled) {
SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db);

View File

@ -136,6 +136,11 @@ class CreateEntityPdf implements ShouldQueue
$entity_design_id = 2;
$design = Design::find($entity_design_id);
/* Catch all in case migration doesn't pass back a valid design */
if(!$design)
$design = Design::find(2);
$html = new HtmlEngine($this->invitation);
if ($design->is_custom) {

View File

@ -101,7 +101,8 @@ class NinjaMailerJob implements ShouldQueue
//send email
try {
nlog("trying to send to {$this->nmo->to_user->email} ". now()->toDateTimeString());
nlog("Using mailer => ". $this->mailer);
Mail::mailer($this->mailer)
->to($this->nmo->to_user->email)
->send($this->nmo->mailable);
@ -146,11 +147,7 @@ class NinjaMailerJob implements ShouldQueue
{
/* Singletons need to be rebooted each time just in case our Locale is changing*/
App::forgetInstance('translator');
// App::forgetInstance('mail.manager'); //singletons must be destroyed!
// App::forgetInstance('mailer');
// App::forgetInstance('laravelgmail');
$t = app('translator');
/* Inject custom translations if any exist */
$t->replace(Ninja::transformTranslations($this->nmo->settings));
switch ($this->nmo->settings->email_sending_method) {
@ -165,7 +162,6 @@ class NinjaMailerJob implements ShouldQueue
break;
}
(new MailServiceProvider(app()))->register();
}
private function setGmailMailer()
@ -182,7 +178,14 @@ class NinjaMailerJob implements ShouldQueue
$google = (new Google())->init();
try{
if ($google->getClient()->isAccessTokenExpired()) {
$google->refreshToken($user);
$user = $user->fresh();
}
$google->getClient()->setAccessToken(json_encode($user->oauth_user_token));
}
catch(\Exception $e) {
$this->logMailError('Gmail Token Invalid', $this->company->clients()->first());
@ -190,9 +193,7 @@ class NinjaMailerJob implements ShouldQueue
return $this->setMailDriver();
}
if ($google->getClient()->isAccessTokenExpired()) {
$google->refreshToken($user);
}
/*
* Now that our token is refreshed and valid we can boot the

View File

@ -55,10 +55,15 @@ class CompanySizeCheck implements ShouldQueue
private function check()
{
Company::cursor()->each(function ($company) {
if ($company->invoices->count() > 1000 || $company->products->count() > 1000 || $company->clients->count() > 1000) {
if ($company->invoices()->count() > 1000 || $company->products()->count() > 1000 || $company->clients()->count() > 1000) {
nlog("Marking company {$company->id} as large");
$company->is_large = true;
$company->save();
}
});
}
}

View File

@ -337,6 +337,10 @@ class Import implements ShouldQueue
if(!MultiDB::checkDomainAvailable($data['subdomain']))
$data['subdomain'] = MultiDB::randomSubdomainGenerator();
if(strlen($data['subdomain']) == 0)
$data['subdomain'] = MultiDB::randomSubdomainGenerator();
}
$rules = (new UpdateCompanyRequest())->rules();

View File

@ -56,6 +56,7 @@ class ReminderJob implements ShouldQueue
nlog("Sending invoice reminders " . now()->format('Y-m-d h:i:s'));
Invoice::where('next_send_date', '<=', now()->toDateTimeString())
->whereNull('deleted_at')
->where('is_deleted', 0)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('balance', '>', 0)

View File

@ -25,14 +25,21 @@ class ACHVerificationNotification extends Mailable
*/
public $company;
/**
* @var string
*/
public $url;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct(Company $company)
public function __construct(Company $company, string $url)
{
$this->company = $company;
$this->url = $url;
}
/**

View File

@ -36,7 +36,7 @@ class MigrationCompleted extends Mailable
$data['settings'] = $this->company->settings;
$data['company'] = $this->company->fresh();
$data['whitelabel'] = $this->company->account->isPaid() ? true : false;
$data['check_data'] = $this->check_data;
$data['check_data'] = $this->check_data ?: '';
$data['logo'] = $this->company->present()->logo();
$result = $this->from(config('mail.from.address'), config('mail.from.name'))

View File

@ -49,8 +49,11 @@ class TemplateEmail extends Mailable
public function build()
{
// $template_name = 'email.template.'.$this->build_email->getTemplate();
$template_name = 'email.template.client';
$template_name = 'email.template.'.$this->build_email->getTemplate();
if ($this->build_email->getTemplate() == 'light' || $this->build_email->getTemplate() == 'dark') {
$template_name = 'email.template.client';
}
if($this->build_email->getTemplate() == 'custom') {
$this->build_email->setBody(str_replace('$body', $this->build_email->getBody(), $this->client->getSetting('email_style_custom')));
@ -62,10 +65,6 @@ class TemplateEmail extends Mailable
$this->build_email->setBody(
DesignHelpers::parseMarkdownToHtml($this->build_email->getBody())
);
$this->build_email->setBody(
TemplateEngine::wrapElementsIntoTables('<div id="content-wrapper"></div>', $this->build_email->getBody(), $settings)
);
}
$company = $this->client->company;

View File

@ -224,6 +224,13 @@ class RecurringInvoice extends BaseModel
$offset = $this->client->timezone_offset();
/*
As we are firing at UTC+0 if our offset is negative it is technically firing the day before so we always need
to add ON a day - a day = 86400 seconds
*/
if($offset < 0)
$offset += 86400;
switch ($this->frequency_id) {
case self::FREQUENCY_DAILY:
return Carbon::parse($this->next_send_date)->startOfDay()->addDay()->addSeconds($offset);
@ -428,17 +435,14 @@ class RecurringInvoice extends BaseModel
'due_date' => $next_due_date_string
];
$next_send_date = $this->nextDateByFrequency($next_send_date->format('Y-m-d'));
/* Fixes the timeshift in case the offset is negative which cause a infinite loop due to UTC +0*/
if($this->client->timezone_offset() < 0){
$next_send_date = $this->nextDateByFrequency($next_send_date->addDay()->format('Y-m-d'));
}
else
$next_send_date = $this->nextDateByFrequency($next_send_date->format('Y-m-d'));
}
/*If no due date is set - unset the due_date value */
// if(!$this->due_date_days || $this->due_date_days == 0){
// foreach($data as $key => $value)
// $data[$key]['due_date'] = '';
// }
return $data;
}

View File

@ -211,7 +211,7 @@ class PayPalExpressPaymentDriver extends BaseDriver
return new Item([
'name' => $lineItem->product_key,
'description' => substr($lineItem->notes, 0, 100),
'description' => substr(strip_tags($lineItem->notes), 0, 100),
'price' => $lineItem->cost,
'quantity' => $lineItem->quantity,
]);

View File

@ -13,6 +13,7 @@
namespace App\PaymentDrivers\Stripe;
use App\Exceptions\PaymentFailed;
use App\Http\Requests\ClientPortal\PaymentMethod\VerifyPaymentMethodRequest;
use App\Http\Requests\Request;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
@ -67,7 +68,7 @@ class ACH
$client_gateway_token = $this->storePaymentMethod($source, $request->input('method'), $customer);
$mailer = new NinjaMailerObject();
$mailer->mailable = new ACHVerificationNotification(auth('contact')->user()->client->company);
$mailer->mailable = new ACHVerificationNotification(auth('contact')->user()->client->company, route('client.payment_methods.verification', ['payment_method' => $client_gateway_token->hashed_id, 'method' => GatewayType::BANK_TRANSFER]));
$mailer->company = auth('contact')->user()->client->company;
$mailer->settings = auth('contact')->user()->client->company->settings;
$mailer->to_user = auth('contact')->user();
@ -79,6 +80,12 @@ class ACH
public function verificationView(ClientGatewayToken $token)
{
if (isset($token->meta->state) && $token->meta->state === 'authorized') {
return redirect()
->route('client.payment_methods.show', $token->hashed_id)
->with('message', __('texts.payment_method_verified'));
}
$data = [
'token' => $token,
'gateway' => $this->stripe,
@ -87,8 +94,14 @@ class ACH
return render('gateways.stripe.ach.verify', $data);
}
public function processVerification(Request $request, ClientGatewayToken $token)
public function processVerification($request, ClientGatewayToken $token)
{
if (isset($token->meta->state) && $token->meta->state === 'authorized') {
return redirect()
->route('client.payment_methods.show', $token->hashed_id)
->with('message', __('texts.payment_method_verified'));
}
$this->stripe->init();
$bank_account = Customer::retrieveSource($request->customer, $request->source, $this->stripe->stripe_connect_auth);
@ -96,12 +109,14 @@ class ACH
try {
$bank_account->verify(['amounts' => request()->transactions]);
$token->meta->verified_at = now();
$meta = $token->meta;
$meta->state = 'authorized';
$token->meta = $meta;
$token->save();
return redirect()
->route('client.invoices.index')
->with('success', __('texts.payment_method_verified'));
->route('client.payment_methods.show', $token->hashed_id)
->with('message', __('texts.payment_method_verified'));
} catch (CardException $e) {
return back()->with('error', $e->getMessage());
}

View File

@ -161,7 +161,7 @@ class ACH
{
$meta = $token->meta;
$meta->state = $response->state;
$token->meta;
$token->meta = $meta;
$token->save();
return redirect()->route('client.payment_methods.index');
@ -189,7 +189,7 @@ class ACH
public function paymentResponse($request)
{
nlog($request->all());
// nlog($request->all());
$token = ClientGatewayToken::find($this->decodePrimaryKey($request->input('source')));
$token_meta = $token->meta;
@ -197,14 +197,20 @@ class ACH
if($token_meta->state != "authorized")
return redirect()->route('client.payment_methods.verification', ['payment_method' => $token->hashed_id, 'method' => GatewayType::BANK_TRANSFER]);
$app_fee = (config('ninja.wepay.fee_ach_multiplier') * $this->wepay_payment_driver->payment_hash->data->amount_with_fee) + config('ninja.wepay.fee_fixed');
$response = $this->wepay_payment_driver->wepay->request('checkout/create', array(
// 'callback_uri' => route('payment_webhook', ['company_key' => $this->wepay_payment_driver->company_gateway->company->company_key, 'company_gateway_id' => $this->wepay_payment_driver->company_gateway->hashed_id]),
'unique_id' => Str::random(40),
'account_id' => $this->wepay_payment_driver->company_gateway->getConfigField('accountId'),
'amount' => $this->wepay_payment_driver->payment_hash->data->amount_with_fee,
'currency' => $this->wepay_payment_driver->client->getCurrencyCode(),
'short_description' => 'A vacation home rental',
'short_description' => 'Goods and Services',
'type' => 'goods',
'fee' => [
'fee_payer' => config('ninja.wepay.fee_payer'),
'app_fee' => $app_fee,
],
'payment_method' => array(
'type' => 'payment_bank',
'payment_bank' => array(

View File

@ -139,16 +139,21 @@ use WePayCommon;
}
// USD, CAD, and GBP.
nlog($request->all());
// nlog($request->all());
$app_fee = (config('ninja.wepay.fee_cc_multiplier') * $this->wepay_payment_driver->payment_hash->data->amount_with_fee) + config('ninja.wepay.fee_fixed');
// charge the credit card
$response = $this->wepay_payment_driver->wepay->request('checkout/create', array(
// 'callback_uri' => route('payment_webhook', ['company_key' => $this->wepay_payment_driver->company_gateway->company->company_key, 'company_gateway_id' => $this->wepay_payment_driver->company_gateway->hashed_id]),
'unique_id' => Str::random(40),
'account_id' => $this->wepay_payment_driver->company_gateway->getConfigField('accountId'),
'amount' => $this->wepay_payment_driver->payment_hash->data->amount_with_fee,
'currency' => $this->wepay_payment_driver->client->getCurrencyCode(),
'short_description' => 'A vacation home rental',
'short_description' => 'Goods and services',
'type' => 'goods',
'fee' => [
'fee_payer' => config('ninja.wepay.fee_payer'),
'app_fee' => $app_fee,
],
'payment_method' => array(
'type' => 'credit_card',
'credit_card' => array(

View File

@ -24,8 +24,13 @@ trait WePayCommon
private function processSuccessfulPayment($response, $payment_status, $gateway_type)
{
if($gateway_type == GatewayType::BANK_TRANSFER)
$payment_type = PaymentType::ACH;
else
$payment_type = PaymentType::CREDIT_CARD_OTHER;
$data = [
'payment_type' => PaymentType::CREDIT_CARD_OTHER,
'payment_type' => $payment_type,
'amount' => $response->amount,
'transaction_reference' => $response->checkout_id,
'gateway_type_id' => $gateway_type,

View File

@ -266,18 +266,17 @@ class WePayPaymentDriver extends BaseDriver
$response = $this->wepay->request('checkout/refund', array(
'checkout_id' => $payment->transaction_reference,
'refund_reason' => 'Refund',
'refund_reason' => 'Refund by merchant',
'amount' => $amount
));
return [
'transaction_reference' => $response->checkout_id,
'transaction_response' => json_encode($response),
'success' => $response->state == 'refunded' ? true : false,
'description' => 'refund',
'code' => 0,
];
return [
'transaction_reference' => $response->checkout_id,
'transaction_response' => json_encode($response),
'success' => $response->state == 'refunded' ? true : false,
'description' => 'refund',
'code' => 0,
];
}

View File

@ -11,12 +11,14 @@
namespace App\Repositories;
use App\Models\Client;
use App\Models\GroupSetting;
class GroupSettingRepository extends BaseRepository
{
public function save($data, GroupSetting $group_setting) :?GroupSetting
{
$group_setting->fill($data);
$group_setting->save();
@ -27,6 +29,15 @@ class GroupSettingRepository extends BaseRepository
$group_setting->save();
}
nlog($data['settings']);
if(count((array)$data['settings']) == 0){
$settings = new \stdClass;
$settings->entity = Client::class;
$group_setting->settings = $settings;
$group_setting->save();
}
return $group_setting;
}
}

View File

@ -208,7 +208,9 @@ class PaymentMigrationRepository extends BaseRepository
$exchange_rate = new CurrencyApi();
$payment->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, Carbon::parse($payment->date));
$payment->exchange_currency_id = $client_currency;
// $payment->exchange_currency_id = $client_currency;
$payment->exchange_currency_id = $company_currency;
}
return $payment;

View File

@ -199,7 +199,9 @@ class PaymentRepository extends BaseRepository {
$exchange_rate = new CurrencyApi();
$payment->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, Carbon::parse($payment->date));
$payment->exchange_currency_id = $client_currency;
// $payment->exchange_currency_id = $client_currency;
$payment->exchange_currency_id = $company_currency;
}
return $payment;

View File

@ -13,6 +13,7 @@
namespace App\Repositories;
use App\DataMapper\ClientSettings;
use App\DataMapper\InvoiceItem;
use App\Factory\InvoiceFactory;
use App\Models\Client;
@ -44,8 +45,8 @@ class SubscriptionRepository extends BaseRepository
private function calculatePrice($subscription) :array
{
DB::beginTransaction();
// DB::beginTransaction();
DB::connection(config('database.default'))->beginTransaction();
$data = [];
$client = Client::factory()->create([
@ -53,6 +54,7 @@ class SubscriptionRepository extends BaseRepository
'company_id' => $subscription->company_id,
'group_settings_id' => $subscription->group_id,
'country_id' => $subscription->company->settings->country_id,
'settings' => ClientSettings::defaults(),
]);
$contact = ClientContact::factory()->create([
@ -88,8 +90,9 @@ class SubscriptionRepository extends BaseRepository
$data['promo_price'] = $invoice->calc()->getTotal();
DB::rollBack();
// DB::rollBack();
DB::connection(config('database.default'))->rollBack();
return $data;
}

View File

@ -377,23 +377,28 @@ class InvoiceService
{
switch ($reminder_template) {
case 'reminder1':
$this->invoice->reminder1_sent = now()->format('Y-m-d');
$this->invoice->reminder_last_sent = now()->format('Y-m-d');
$this->invoice->reminder1_sent = now();
$this->invoice->reminder_last_sent = now();
$this->invoice->last_sent_date = now();
break;
case 'reminder2':
$this->invoice->reminder2_sent = now()->format('Y-m-d');
$this->invoice->reminder_last_sent = now()->format('Y-m-d');
$this->invoice->reminder2_sent = now();
$this->invoice->reminder_last_sent = now();
$this->invoice->last_sent_date = now();
break;
case 'reminder3':
$this->invoice->reminder3_sent = now()->format('Y-m-d');
$this->invoice->reminder_last_sent = now()->format('Y-m-d');
$this->invoice->reminder3_sent = now();
$this->invoice->reminder_last_sent = now();
$this->invoice->last_sent_date = now();
break;
case 'endless_reminder':
$this->invoice->reminder_last_sent = now()->format('Y-m-d');
$this->invoice->reminder_last_sent = now();
$this->invoice->last_sent_date = now();
break;
default:
$this->invoice->reminder1_sent = now()->format('Y-m-d');
$this->invoice->reminder_last_sent = now()->format('Y-m-d');
$this->invoice->reminder1_sent = now();
$this->invoice->reminder_last_sent = now();
$this->invoice->last_sent_date = now();
break;
}

View File

@ -113,7 +113,9 @@ class MarkPaid extends AbstractService
$exchange_rate = new CurrencyApi();
$payment->exchange_rate = $exchange_rate->exchangeRate($client_currency, $company_currency, Carbon::parse($payment->date));
$payment->exchange_currency_id = $client_currency;
//$payment->exchange_currency_id = $client_currency; // 23/06/2021
$payment->exchange_currency_id = $company_currency;
$payment->save();
}

View File

@ -12,6 +12,7 @@
namespace App\Services\Invoice;
use App\Models\Invoice;
use App\Models\RecurringInvoice;
use App\Services\AbstractService;
use Carbon\Carbon;
@ -126,6 +127,14 @@ class UpdateReminder extends AbstractService
$date_collection->push($reminder_date);
}
if ($this->invoice->last_sent_date &&
(int)$this->settings->endless_reminder_frequency_id > 0) {
$reminder_date = $this->addTimeInterval($this->invoice->last_sent_date, (int)$this->settings->num_days_reminder3)->addSeconds($offset);
if ($reminder_date->gt(Carbon::parse($this->invoice->next_send_date)));
$date_collection->push($reminder_date);
}
if($date_collection->count() >=1 && $date_collection->sort()->first()->gte(now()))
$this->invoice->next_send_date = $date_collection->sort()->first();
else
@ -133,4 +142,41 @@ class UpdateReminder extends AbstractService
return $this->invoice;
}
private function addTimeInterval($date, $endless_reminder_frequency_id) :?Carbon
{
if (!$date)
return null;
switch ($endless_reminder_frequency_id) {
case RecurringInvoice::FREQUENCY_DAILY:
return Carbon::parse($date)->addDay()->startOfDay();
case RecurringInvoice::FREQUENCY_WEEKLY:
return Carbon::parse($date)->addWeek()->startOfDay();
case RecurringInvoice::FREQUENCY_TWO_WEEKS:
return Carbon::parse($date)->addWeeks(2)->startOfDay();
case RecurringInvoice::FREQUENCY_FOUR_WEEKS:
return Carbon::parse($date)->addWeeks(4)->startOfDay();
case RecurringInvoice::FREQUENCY_MONTHLY:
return Carbon::parse($date)->addMonthNoOverflow()->startOfDay();
case RecurringInvoice::FREQUENCY_TWO_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(2)->startOfDay();
case RecurringInvoice::FREQUENCY_THREE_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(3)->startOfDay();
case RecurringInvoice::FREQUENCY_FOUR_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(4)->startOfDay();
case RecurringInvoice::FREQUENCY_SIX_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(6)->startOfDay();
case RecurringInvoice::FREQUENCY_ANNUALLY:
return Carbon::parse($date)->addYear()->startOfDay();
case RecurringInvoice::FREQUENCY_TWO_YEARS:
return Carbon::parse($date)->addYears(2)->startOfDay();
case RecurringInvoice::FREQUENCY_THREE_YEARS:
return Carbon::parse($date)->addYears(3)->startOfDay();
default:
return null;
}
}
}

View File

@ -137,10 +137,6 @@ class Design extends BaseDesign
$elements[] = ['element' => 'p', 'content' => $variable, 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . substr($variable, 1)]];
}
foreach (['company1', 'company2', 'company3', 'company4'] as $field) {
$elements[] = ['element' => 'p', 'content' => $this->getCustomFieldValue($field), 'show_empty' => false, 'properties' => ['data-ref' => 'company_details-' . $field]];
}
return $elements;
}
@ -443,7 +439,7 @@ class Design extends BaseDesign
['element' => 'span', 'content' => '$entity.terms_label: ', 'properties' => ['hidden' => $this->entityVariableCheck('$entity.terms'), 'data-ref' => 'total_table-terms-label', 'style' => 'font-weight: bold; text-align: left; margin-top: 1rem;']],
['element' => 'span', 'content' => strtr($_variables['values']['$entity.terms'], $_variables), 'properties' => ['data-ref' => 'total_table-terms', 'style' => 'text-align: left;']],
]],
['element' => 'img', 'properties' => ['hidden' => $this->client->getSetting('signature_on_pdf'), 'style' => 'max-width: 50%; height: auto;', 'src' => '$contact.signature']],
['element' => 'img', 'properties' => ['style' => 'max-width: 50%; height: auto;', 'src' => '$contact.signature', 'id' => 'contact-signature']],
['element' => 'div', 'properties' => ['style' => 'margin-top: 1.5rem; display: flex; align-items: flex-start;'], 'elements' => [
['element' => 'img', 'properties' => ['src' => '$invoiceninja.whitelabel', 'style' => 'height: 2.5rem;', 'hidden' => $this->entity->user->account->isPaid() ? 'true' : 'false', 'id' => 'invoiceninja-whitelabel-logo']],
]],
@ -501,11 +497,11 @@ class Design extends BaseDesign
} elseif (Str::startsWith($variable, '$custom_surcharge')) {
$_variable = ltrim($variable, '$'); // $custom_surcharge1 -> custom_surcharge1
$visible = $this->entity->{$_variable} == '0';
$visible = $this->entity->{$_variable} > 0 || $this->entity->{$_variable} > '0';
$elements[1]['elements'][] = ['element' => 'div', 'elements' => [
['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => $visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']],
['element' => 'span', 'content' => $variable, 'properties' => ['hidden' => $visible, 'data-ref' => 'totals_table-' . substr($variable, 1)]],
['element' => 'span', 'content' => $variable . '_label', 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1) . '-label']],
['element' => 'span', 'content' => $variable, 'properties' => ['hidden' => !$visible, 'data-ref' => 'totals_table-' . substr($variable, 1)]],
]];
} elseif (Str::startsWith($variable, '$custom')) {
$field = explode('_', $variable);

View File

@ -109,11 +109,15 @@ class HtmlEngine
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.date')];
$data['$invoice.date'] = &$data['$date'];
$data['$invoiceDate'] = &$data['$date'];
$data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')];
$data['$dueDate'] = &$data['$due_date'];
$data['$payment_due'] = ['value' => $this->translateDate($this->entity->due_date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.payment_due')];
$data['$invoice.due_date'] = &$data['$due_date'];
$data['$invoice.number'] = ['value' => $this->entity->number ?: '&nbsp;', 'label' => ctrans('texts.invoice_number')];
$data['$invoice.po_number'] = ['value' => $this->entity->po_number ?: '&nbsp;', 'label' => ctrans('texts.po_number')];
$data['$poNumber'] = &$data['$invoice.po_number'];
$data['$entity.datetime'] = ['value' => $this->formatDatetime($this->entity->created_at, $this->entity->client->date_format(), $this->entity->client->locale()), 'label' => ctrans('texts.date')];
$data['$invoice.datetime'] = &$data['$entity.datetime'];
$data['$quote.datetime'] = &$data['$entity.datetime'];
@ -125,6 +129,7 @@ class HtmlEngine
$data['$entity.terms'] = ['value' => $this->entity->terms ?: '', 'label' => ctrans('texts.invoice_terms')];
$data['$terms'] = &$data['$entity.terms'];
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_invoice').'</a>', 'label' => ctrans('texts.view_invoice')];
$data['$viewLink'] = &$data['$view_link'];
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_invoice')];
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.invoice_date')];
@ -139,6 +144,7 @@ class HtmlEngine
$data['$entity.terms'] = ['value' => $this->entity->terms ?: '', 'label' => ctrans('texts.quote_terms')];
$data['$terms'] = &$data['$entity.terms'];
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_quote').'</a>', 'label' => ctrans('texts.view_quote')];
$data['$viewLink'] = &$data['$view_link'];
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_quote')];
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.quote_date')];
}
@ -149,6 +155,7 @@ class HtmlEngine
$data['$entity.terms'] = ['value' => $this->entity->terms ?: '', 'label' => ctrans('texts.credit_terms')];
$data['$terms'] = &$data['$entity.terms'];
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_credit').'</a>', 'label' => ctrans('texts.view_credit')];
$data['$viewLink'] = &$data['$view_link'];
$data['$view_url'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')];
// $data['$view_link'] = ['value' => $this->invitation->getLink(), 'label' => ctrans('texts.view_credit')];
$data['$date'] = ['value' => $this->translateDate($this->entity->date, $this->entity->client->date_format(), $this->entity->client->locale()) ?: '&nbsp;', 'label' => ctrans('texts.credit_date')];
@ -171,11 +178,13 @@ class HtmlEngine
}
$data['$quote.balance_due'] = $data['$balance_due'];
$data['$invoice.balance_due'] = $data['$balance_due'];
$data['$quote.balance_due'] = &$data['$balance_due'];
$data['$invoice.balance_due'] = &$data['$balance_due'];
// $data['$balance_due'] = $data['$balance_due'];
$data['$outstanding'] = $data['$balance_due'];
$data['$outstanding'] = &$data['$balance_due'];
$data['$partial_due'] = ['value' => Number::formatMoney($this->entity->partial, $this->client) ?: '&nbsp;', 'label' => ctrans('texts.partial_due')];
$data['$partial'] = &$data['$partial_due'];
$data['$total'] = ['value' => Number::formatMoney($this->entity_calc->getTotal(), $this->client) ?: '&nbsp;', 'label' => ctrans('texts.total')];
$data['$amount'] = &$data['$total'];
$data['$amount_due'] = ['value' => &$data['$total']['value'], 'label' => ctrans('texts.amount_due')];
@ -205,6 +214,7 @@ class HtmlEngine
$data['$invoice.public_notes'] = ['value' => $this->entity->public_notes ?: '', 'label' => ctrans('texts.public_notes')];
$data['$entity.public_notes'] = &$data['$invoice.public_notes'];
$data['$public_notes'] = &$data['$invoice.public_notes'];
$data['$notes'] = &$data['$public_notes'];
$data['$entity_issued_to'] = ['value' => '', 'label' => ctrans("texts.{$this->entity_string}_issued_to")];
$data['$your_entity'] = ['value' => '', 'label' => ctrans("texts.your_{$this->entity_string}")];
@ -249,6 +259,8 @@ class HtmlEngine
$data['$email'] = ['value' => isset($this->contact) ? $this->contact->email : 'no contact email on record', 'label' => ctrans('texts.email')];
$data['$client_name'] = ['value' => $this->entity->present()->clientName() ?: '&nbsp;', 'label' => ctrans('texts.client_name')];
$data['$client.name'] = &$data['$client_name'];
$data['$client'] = &$data['$client_name'];
$data['$client.address1'] = &$data['$address1'];
$data['$client.address2'] = &$data['$address2'];
$data['$client_address'] = ['value' => $this->client->present()->address() ?: '&nbsp;', 'label' => ctrans('texts.address')];
@ -272,11 +284,15 @@ class HtmlEngine
$data['$paid_to_date'] = ['value' => Number::formatMoney($this->entity->paid_to_date, $this->client), 'label' => ctrans('texts.paid_to_date')];
$data['$contact.full_name'] = ['value' => $this->contact->present()->name(), 'label' => ctrans('texts.name')];
$data['$contact'] = &$data['$contact.full_name'];
$data['$contact.email'] = ['value' => $this->contact->email, 'label' => ctrans('texts.email')];
$data['$contact.phone'] = ['value' => $this->contact->phone, 'label' => ctrans('texts.phone')];
$data['$contact.name'] = ['value' => isset($this->contact) ? $this->contact->present()->name() : $this->client->present()->name(), 'label' => ctrans('texts.contact_name')];
$data['$contact.first_name'] = ['value' => isset($this->contact) ? $this->contact->first_name : '', 'label' => ctrans('texts.first_name')];
$data['$firstName'] = &$data['$contact.first_name'];
$data['$contact.last_name'] = ['value' => isset($this->contact) ? $this->contact->last_name : '', 'label' => ctrans('texts.last_name')];
@ -288,6 +304,8 @@ class HtmlEngine
$data['$company.city_state_postal'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, false) ?: '&nbsp;', 'label' => ctrans('texts.city_state_postal')];
$data['$company.postal_city_state'] = ['value' => $this->company->present()->cityStateZip($this->settings->city, $this->settings->state, $this->settings->postal_code, true) ?: '&nbsp;', 'label' => ctrans('texts.postal_city_state')];
$data['$company.name'] = ['value' => $this->settings->name ?: ctrans('texts.untitled_account'), 'label' => ctrans('texts.company_name')];
$data['$account'] = &$data['$company.name'];
$data['$company.address1'] = ['value' => $this->settings->address1 ?: '&nbsp;', 'label' => ctrans('texts.address1')];
$data['$company.address2'] = ['value' => $this->settings->address2 ?: '&nbsp;', 'label' => ctrans('texts.address2')];
$data['$company.city'] = ['value' => $this->settings->city ?: '&nbsp;', 'label' => ctrans('texts.city')];
@ -302,6 +320,7 @@ class HtmlEngine
$data['$company.address'] = ['value' => $this->company->present()->address($this->settings) ?: '&nbsp;', 'label' => ctrans('texts.address')];
$data['$signature'] = ['value' => $this->settings->email_signature ?: '&nbsp;', 'label' => ''];
$data['$emailSignature'] = &$data['$signature'];
$data['$spc_qr_code'] = ['value' => $this->company->present()->getSpcQrCode($this->client->currency()->code, $this->entity->number, $this->entity->balance, $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'company1', $this->settings->custom_value1, $this->client)), 'label' => ''];
@ -393,6 +412,8 @@ class HtmlEngine
$data['$page_layout'] = ['value' => property_exists($this->settings, 'page_layout') ? $this->settings->page_layout : 'Portrait', 'label' => ''];
$data['$tech_hero_image'] = ['value' => asset('images/pdf-designs/tech-hero-image.jpg'), 'label' => ''];
$data['$autoBill'] = ['value' => ctrans('texts.auto_bill_notification_placeholder'), 'label' => ''];
$data['$auto_bill'] = &$data['$autoBill'];
$arrKeysLength = array_map('strlen', array_keys($data));
array_multisort($arrKeysLength, SORT_DESC, $data);

View File

@ -212,7 +212,7 @@ class TemplateEngine
} else {
$wrapper = '';
}
}
}
elseif ($email_style == 'plain') {
$wrapper = view($this->getTemplatePath($email_style), $data)->render();
$injection = '';
@ -226,7 +226,7 @@ class TemplateEngine
$data = [
'subject' => $this->subject,
'body' => $email_style == 'custom' ? $this->body : self::wrapElementsIntoTables(strtr('<div id="content-wrapper"></div>', ['$body' => '']), $this->body, $this->entity_obj->client->getMergedSettings()),
'body' => $this->body,
'wrapper' => $wrapper,
'raw_body' => $this->raw_body,
'raw_subject' => $this->raw_subject
@ -239,7 +239,7 @@ class TemplateEngine
private function mockEntity()
{
DB::beginTransaction();
DB::connection(config('database.default'))->beginTransaction();
$client = Client::factory()->create([
'user_id' => auth()->user()->id,
@ -277,52 +277,6 @@ class TemplateEngine
private function tearDown()
{
DB::rollBack();
}
public static function wrapElementsIntoTables(string $wrapper, string $body, $settings): ?string
{
$documents['wrapper'] = new \DOMDocument();
@$documents['wrapper']->loadHTML($wrapper);
$documents['master'] = new \DOMDocument();
$documents['master']->loadHTML(
view('email.template.master', ['header' => '', 'slot' => '', 'settings' => $settings])->render()
);
$styles = $documents['master']->getElementsByTagName('style')->item(0)->nodeValue;
$documents['wrapper']->saveHTML();
$documents['body'] = new \DOMDocument();
$documents['body']->loadHTML(empty($body) ? '<div></div>' : mb_convert_encoding((new CssToInlineStyles())->convert($body, $styles), 'HTML-ENTITIES', 'UTF-8'));
$table_html ='
<table style="font-family:arial,helvetica,sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
<tbody>
<tr>
<td style="overflow-wrap:break-word;word-break:break-word;padding:5px;font-family:arial,helvetica,sans-serif;" align="left">
<div style="color: #000000; line-height: 140%; text-align: left; word-wrap: break-word;" id="table-content" class="content-contrast-color"></div>
</td>
</tr>
</tbody>
</table>';
foreach ($documents['body']->getElementsByTagName('body')->item(0)->childNodes as $element) {
$table = new \DOMDocument();
$table->loadHTML($table_html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
$element = $table->importNode($element, true);
$table->getElementById('table-content')->appendChild($element);
$node = $documents['wrapper']->importNode($table->documentElement, true);
$documents['wrapper']->getElementById('content-wrapper')->appendChild($node);
}
return $documents['wrapper']->getElementById('content-wrapper')->ownerDocument->saveHTML($documents['wrapper']->getElementById('content-wrapper'));
DB::connection(config('database.default'))->rollBack();
}
}

View File

@ -568,13 +568,21 @@ trait GeneratesCounter
$search[] = '{$client_counter}';
$replace[] = $counter;
$search[] = '{$clientCounter}';
$replace[] = $counter;
$search[] = '{$group_counter}';
$replace[] = $counter;
if (strstr($pattern, '{$user_id}')) {
$search[] = '{$year}';
$replace[] = date('Y');
if (strstr($pattern, '{$user_id}') || strstr($pattern, '{$userId}')) {
$user_id = $entity->user_id ? $entity->user_id : 0;
$search[] = '{$user_id}';
$replace[] = str_pad(($user_id), 2, '0', STR_PAD_LEFT);
$search[] = '{$userId}';
$replace[] = str_pad(($user_id), 2, '0', STR_PAD_LEFT);
}
$matches = false;
@ -624,9 +632,15 @@ trait GeneratesCounter
$search[] = '{$client_custom1}';
$replace[] = $client->custom_value1;
$search[] = '{$clientCustom1}';
$replace[] = $client->custom_value1;
$search[] = '{$client_custom2}';
$replace[] = $client->custom_value2;
$search[] = '{$clientCustom2}';
$replace[] = $client->custom_value2;
$search[] = '{$client_custom3}';
$replace[] = $client->custom_value3;
@ -638,6 +652,9 @@ trait GeneratesCounter
$search[] = '{$client_id_number}';
$replace[] = $client->id_number;
$search[] = '{$clientIdNumber}';
$replace[] = $client->id_number;
}
return str_replace($search, $replace, $pattern);

View File

@ -474,7 +474,7 @@ trait MakesInvoiceValues
}
if ($matches->keys()->first() == ':MONTH') {
$output = \Carbon\Carbon::create()->month($output)->localeMonth;
$output = \Carbon\Carbon::create()->month($output)->translatedFormat('F');
}
$value = preg_replace(

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.2.6',
'app_tag' => '5.2.6-release',
'app_version' => '5.2.7',
'app_tag' => '5.2.7-release',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),
@ -152,6 +152,10 @@ return [
'environment' => env('WEPAY_ENVIRONMENT', 'stage'),
'client_id' => env('WEPAY_CLIENT_ID', ''),
'client_secret' => env('WEPAY_CLIENT_SECRET',''),
'fee_payer' => env('WEPAY_FEE_PAYER'),
'fee_cc_multiplier' => env('WEPAY_APP_FEE_CC_MULTIPLIER'),
'fee_ach_multiplier' => env('WEPAY_APP_FEE_ACH_MULTIPLIER'),
'fee_fixed' => env('WEPAY_APP_FEE_FIXED'),
],
'ninja_stripe_publishable_key' => env('NINJA_PUBLISHABLE_KEY', null),
'ninja_stripe_client_id' => env('NINJA_STRIPE_CLIENT_ID', null),

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class PaymentsTableCurrencyNullable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('payments', function (Blueprint $table){
$table->unsignedInteger('exchange_currency_id')->nullable()->change();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
//
}
}

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

View File

@ -31,7 +31,7 @@ const RESOURCES = {
"assets/NOTICES": "687b68d41e137cfbdee105c0b9be3e9d",
"assets/FontManifest.json": "cf3c681641169319e61b61bd0277378f",
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
"main.dart.js": "1225d7da63eaf3817e25a8b6726635cc",
"main.dart.js": "383f48eff49849cbbe38e2fe4ae81ad4",
"/": "23224b5e03519aaa87594403d54412cf"
};

112046
public/main.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

106878
public/main.foss.dart.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"/js/app.js": "/js/app.js?id=696e8203d5e8e7cf5ff5",
"/css/app.css": "/css/app.css?id=b3b5fbe4dbfcef5b5f8e",
"/css/app.css": "/css/app.css?id=d9b987796d537e68bee7",
"/js/clients/invoices/action-selectors.js": "/js/clients/invoices/action-selectors.js?id=a09bb529b8e1826f13b4",
"/js/clients/invoices/payment.js": "/js/clients/invoices/payment.js?id=8ce8955ba775ea5f47d1",
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=0dc8c34010d09195d2f7",

View File

@ -4268,6 +4268,10 @@ $LANG = array(
'company_import_failure_body' => 'There was an error importing the company data, the error message was:',
'recurring_invoice_due_date' => 'Due Date',
'amount_cents' => 'Amount in pennies,pence or cents',
'default_payment_method_label' => 'Default Payment Method',
'default_payment_method' => 'Make this your preferred way of paying.',
'already_default_payment_method' => 'This is your preferred way of paying.',
'auto_bill_disabled' => 'Auto Bill Disabled',
);
return $LANG;

View File

@ -236,7 +236,7 @@ $LANG = array(
'archived_vendors' => 'Successfully archived :count vendors',
'deleted_vendor' => 'Successfully deleted vendor',
'deleted_vendors' => 'Successfully deleted :count vendors',
'confirmation_subject' => 'Invoice Ninja Account Confirmation',
'confirmation_subject' => 'Account Confirmation',
'confirmation_header' => 'Account Confirmation',
'confirmation_message' => 'Please access the link below to confirm your account.',
'invoice_subject' => 'New invoice :number from :account',
@ -887,7 +887,7 @@ $LANG = array(
'custom_invoice_charges_helps' => 'Add a field when creating an invoice and include the charge in the invoice subtotals.',
'token_expired' => 'Validation token was expired. Please try again.',
'invoice_link' => 'Invoice Link',
'button_confirmation_message' => 'Click to confirm your email address.',
'button_confirmation_message' => 'Click to confirm your email.',
'confirm' => 'Confirm',
'email_preferences' => 'Email Preferences',
'created_invoices' => 'Successfully created :count invoice(s)',

View File

@ -25,7 +25,7 @@
@endisset
@isset($signature)
{!! $signature !!}
{!! nl2br($signature) !!}
@endisset
</div>
@endcomponent

View File

@ -2,5 +2,7 @@
<div class="center">
<h1>{{ ctrans('texts.ach_verification_notification_label') }}</h1>
<p>{{ ctrans('texts.ach_verification_notification') }}</p>
<a class="button" href="{{ $url }}">{{ ctrans('texts.complete_verification') }}</a>
</div>
@endcomponent

View File

@ -67,7 +67,7 @@
<p><b>{{ ctrans('texts.documents') }}:</b> {{ count($company->documents) }} </p>
@endif
@if($check_data)
@if(isset($check_data))
<p><b>Data Quality:</b></p>
<p> {!! $check_data !!} </p>
@endif

View File

@ -90,6 +90,10 @@
#content .center {
text-align: center;
}
#content .left {
text-align: left !important;
}
</style>
</head>
@ -116,15 +120,11 @@
</tr>
<tr>
<td>
<div style="border: 1px solid #c2c2c2; border-top: none; border-bottom: none; padding: 20px;" id="content">
<div style="border: 1px solid #c2c2c2; border-top: none; border-bottom: none; padding: 20px; text-align: center" id="content">
<div style="padding-top: 10px;"></div>
{{ $slot ?? '' }}
{!! $body ?? '' !!}
@isset($signature)
{{ nl2br($signature) }}
@endisset
<div>
<a href="#"
@ -135,24 +135,27 @@
</td>
</tr>
@if(isset($company) && $company instanceof \App\Models\Company)
<tr>
<td>
<div class="dark-bg dark-text-white"
style="text-align: center; padding-top: 10px; padding-bottom: 25px; background-color: #f9f9f9; border: 1px solid #c2c2c2; border-top: none; border-bottom-color: #f9f9f9;">
<p style="font-size: 15px; color: #2e2e2e; font-family: 'roboto', Arial, Helvetica, sans-serif; font-weight: 400; margin-bottom: 30px;">
{{ ctrans('texts.client_email_company_contact_label') }}
</p>
<p style="font-size: 15px; color: #2e2e2e; font-family: 'roboto', Arial, Helvetica, sans-serif; font-weight: 500; margin-bottom:0;">
{{ $company->present()->name() }}</p>
<p style="font-size: 15px; color: #2e2e2e; font-family: 'roboto', Arial, Helvetica, sans-serif; font-weight: 400; margin-top: 5px;">
<span>{{ $company->settings->phone }}</span>
<span style="font-weight: 500"> {{ $company->settings->website }}</span>
</p>
@isset($signature)
<p style="font-size: 15px; color: #2e2e2e; font-family: 'roboto', Arial, Helvetica, sans-serif; font-weight: 400; margin-bottom: 30px;">
{!! nl2br($signature) !!}
</p>
@endisset
@if(isset($company) && $company instanceof \App\Models\Company)
<p style="font-size: 15px; color: #2e2e2e; font-family: 'roboto', Arial, Helvetica, sans-serif; font-weight: 500; margin-bottom:0;">
{{ $company->present()->name() }}</p>
<p style="font-size: 15px; color: #2e2e2e; font-family: 'roboto', Arial, Helvetica, sans-serif; font-weight: 400; margin-top: 5px;">
<span>{{ $company->settings->phone }}</span>
<span style="font-weight: 500"> {{ $company->settings->website }}</span>
</p>
@endif
</div>
</td>
</tr>
@endif
<tr>
<td>

View File

@ -37,7 +37,7 @@
flex-direction: column;
}
#company-details > span:first-child {
#company-details > p:first-child {
color: var(--primary-color);
}

View File

@ -1,6 +1,6 @@
<div class="grid grid-cols-12">
<div class="col-span-12 lg:col-span-6 bg-gray-50 flex flex-col items-center">
<div class="w-full p-10 lg:w-1/2 lg:mt-24 lg:p-0">
<div class="w-full p-10 lg:mt-24 md:max-w-3xl">
<img class="h-8" src="{{ $subscription->company->present()->logo }}"
alt="{{ $subscription->company->present()->name }}">

View File

@ -0,0 +1,8 @@
<label class="flex items-center cursor-pointer">
<input type="checkbox" class="form-checkbox mr-2"
wire:change="updateAutoBilling" {{ $invoice->auto_bill_enabled ? 'checked' : '' }}>
<span class="text-sm leading-5 font-medium text-gray-900">
{{ $invoice->auto_bill_enabled ? ctrans('texts.auto_bill_enabled') : ctrans('texts.auto_bill_disabled') }}
</span>
</label>

View File

@ -0,0 +1,25 @@
<div class="mt-4 mb-4 bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="sm:flex sm:items-start sm:justify-between">
<div>
<h3 class="text-lg font-medium leading-6 text-gray-900">
{{ ctrans('texts.default_payment_method_label') }}
</h3>
<div class="max-w-xl mt-2 text-sm leading-5 text-gray-500 flex items-center">
<span class="text-primary mr-1 hidden" data-ref="success-label">{{ ctrans('texts.success') }}!</span>
<p>
{{ $token->is_default ? ctrans('texts.already_default_payment_method') : ctrans('texts.default_payment_method') }}
</p>
</div>
</div>
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
<form wire:submit.prevent="makeDefault">
<button class="button button-primary bg-primary" {{ $token->is_default ? 'disabled' : '' }}>
{{ ctrans('texts.save_as_default') }}
</button>
</form>
</div>
</div>
</div>
</div>

View File

@ -15,61 +15,61 @@
<div>
<dl>
@if(!empty($payment_method->gateway_type->name) && !is_null($payment_method->gateway_type->name))
<div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
{{ ctrans('texts.payment_type') }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ ucfirst($payment_method->gateway_type->name) }}
</dd>
</div>
<div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
{{ ctrans('texts.payment_type') }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ ucfirst($payment_method->gateway_type->name) }}
</dd>
</div>
@endif
@if(!empty($payment_method->meta) && !is_null($payment_method->meta))
<div class="px-4 py-5 bg-white sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
{{ ctrans('texts.type') }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ optional($payment_method->meta)->brand }}
{{ optional($payment_method->meta)->scheme }}
</dd>
</div>
<div class="px-4 py-5 bg-white sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
{{ ctrans('texts.type') }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ optional($payment_method->meta)->brand }}
{{ optional($payment_method->meta)->scheme }}
</dd>
</div>
@endif
@if(!empty($payment_method->meta->last4) && !is_null($payment_method->meta->last4))
<div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
{{ ctrans('texts.card_number') }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
**** {{ ucfirst($payment_method->meta->last4) }}
</dd>
</div>
<div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
{{ ctrans('texts.card_number') }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
**** {{ ucfirst($payment_method->meta->last4) }}
</dd>
</div>
@endif
@if(!empty($payment_method->created_at) && !is_null($payment_method->created_at))
<div class="px-4 py-5 bg-white sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
{{ ctrans('texts.date_created') }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ $payment_method->formatDateTimestamp($payment_method->created_at, auth()->user()->client->date_format()) }}
</dd>
</div>
<div class="px-4 py-5 bg-white sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
{{ ctrans('texts.date_created') }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ $payment_method->formatDateTimestamp($payment_method->created_at, auth()->user()->client->date_format()) }}
</dd>
</div>
@endif
@if(!empty($payment_method->is_default) && !is_null($payment_method->is_default))
<div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
{{ ctrans('texts.default') }}
</dt>
<div class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ $payment_method->is_default ? ctrans('texts.yes') : ctrans('texts.no') }}
<div class="px-4 py-5 bg-gray-50 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
{{ ctrans('texts.default') }}
</dt>
<div class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ $payment_method->is_default ? ctrans('texts.yes') : ctrans('texts.no') }}
</div>
</div>
</div>
@endif
@isset($payment_method->meta->exp_month)
<div class="px-4 py-5 bg-white sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
@ -83,7 +83,10 @@
</dl>
</div>
</div>
<div class="mt-4 mb-4 bg-white shadow sm:rounded-lg" translate>
@livewire('payment-methods.update-default-method', ['token' => $payment_method, 'client' => $client])
<div class="mt-4 mb-4 bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="sm:flex sm:items-start sm:justify-between">
<div>
@ -109,3 +112,11 @@
</div>
</div>
@endsection
@section('footer')
<script>
Livewire.on('UpdateDefaultMethod::method-updated', event => {
document.querySelector('span[data-ref=success-label]').classList.remove('hidden');
});
</script>
@endsection

View File

@ -59,6 +59,21 @@
</div>
</div>
@if($invoice->auto_bill === 'optin' || $invoice->auto_bill === 'optout')
<div class="bg-white shadow overflow-hidden lg:rounded-lg mt-4">
<div class="flex flex-col md:flex-row items-start justify-between px-4 py-5 sm:p-6">
<div>
<h3 class="text-lg leading-6 font-medium text-gray-900">Auto Bill</h3>
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500">Change your update bill preferences.</p>
</div>
<div class="flex mt-4 space-x-2">
@livewire('recurring-invoices.update-auto-billing', ['invoice' => $invoice])
</div>
</div>
</div>
@endif
@if(is_null($invoice->subscription_id) || optional($invoice->subscription)->allow_cancellation)
<div class="bg-white shadow sm:rounded-lg mt-4">
<div class="px-4 py-5 sm:p-6">