Merge pull request #6984 from turbo124/v5-stable

v5.3.32
This commit is contained in:
David Bomba 2021-11-19 17:22:46 +11:00 committed by GitHub
commit 54e2ce974e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 105782 additions and 104868 deletions

View File

@ -1 +1 @@
5.3.31
5.3.32

View File

@ -0,0 +1,106 @@
<?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\Console\Commands;
use App\Libraries\MultiDB;
use App\Models\Backup;
use App\Models\Design;
use Illuminate\Console\Command;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Storage;
use stdClass;
class TranslationsExport extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ninja:translations';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Transform translations to json';
private array $langs = [
'ar',
'ca',
'cs',
'da',
'de',
'el',
'en',
'en_GB',
'es',
'es_ES',
'fa',
'fi',
'fr',
'fr_CA',
'hr',
'it',
'ja',
'lt',
'lv_LV',
'mk_MK',
'nb_NO',
'nl',
'pl',
'pt_BR',
'pt_PT',
'ro',
'ru_RU',
'sl',
'sq',
'sv',
'th',
'tr_TR',
'zh_TW'
];
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
Storage::makeDirectory(storage_path('lang'));
foreach($this->langs as $lang)
{
Storage::makeDirectory(storage_path("lang/{$lang}"));
$translations = Lang::getLoader()->load($lang,'texts');
Storage::put(storage_path("lang/{$lang}/{$lang}.json"), json_encode(Arr::dot($translations), JSON_UNESCAPED_UNICODE));
}
}
}

View File

@ -28,7 +28,7 @@ class ClientFactory
$client->public_notes = '';
$client->balance = 0;
$client->paid_to_date = 0;
$client->country_id = 4;
$client->country_id = 840;
$client->is_deleted = 0;
$client->client_hash = Str::random(40);
$client->settings = ClientSettings::defaults();

View File

@ -70,7 +70,7 @@ class GmailTransport extends Transport
if($child->getContentType() != 'text/plain')
{
$this->gmail->attach(TempFile::filePath($child->getBody(), $child->getHeaders()->get('Content-Type')->getParameter('name') ));
$this->gmail->attach(TempFile::filePath($child->getBody(), $child->getHeaders()->get('Content-Type')->getParameter('name') ));
}

View File

@ -58,7 +58,7 @@ class ContactForgotPasswordController extends Controller
*/
public function showLinkRequestForm(Request $request)
{
$account_id = $request->get('account_id');
$account_id = $request->has('account_id') ? $request->get('account_id') : 1;
$account = Account::find($account_id);
$company = $account->companies->first();

View File

@ -36,12 +36,9 @@ class ContactLoginController extends Controller
public function showLoginForm(Request $request)
{
//if we are on the root domain invoicing.co do not show any company logos
// if(Ninja::isHosted() && count(explode('.', request()->getHost())) == 2){
// $company = null;
// }else
$company = false;
$account = false;
if($request->has('company_key')){
MultiDB::findAndSetDbByCompanyKey($request->input('company_key'));
@ -65,13 +62,16 @@ class ContactLoginController extends Controller
}
elseif (Ninja::isSelfHost()) {
$company = Account::first()->default_company;
$account = Account::first();
$company = $account->default_company;
} else {
$company = null;
}
$account_id = $request->get('account_id');
$account = Account::find($account_id);
if(!$account){
$account_id = $request->get('account_id');
$account = Account::find($account_id);
}
return $this->render('auth.login', ['account' => $account, 'company' => $company]);

View File

@ -68,7 +68,7 @@ class ContactResetPasswordController extends Controller
*/
public function showResetForm(Request $request, $token = null)
{
$account_id = $request->get('account_id');
$account_id = $request->has('account_id') ? $request->get('account_id') : 1;
$account = Account::find($account_id);
$db = $account->companies->first()->db;
$company = $account->companies->first();

View File

@ -334,7 +334,7 @@ class BaseController extends Controller
},
'company.expense_categories'=> function ($query) use ($updated_at, $user) {
$query->where('updated_at', '>=', $updated_at);
$query->whereNotNull('updated_at');
},
'company.task_statuses'=> function ($query) use ($updated_at, $user) {
$query->whereNotNull('updated_at');
@ -568,7 +568,7 @@ class BaseController extends Controller
},
'company.expense_categories'=> function ($query) use ($created_at, $user) {
$query->where('created_at', '>=', $created_at);
$query->whereNotNull('created_at');
},
'company.task_statuses'=> function ($query) use ($created_at, $user) {

View File

@ -29,7 +29,9 @@ class SubscriptionController extends Controller
->where('client_id', auth('contact')->user()->client->id)
->where('company_id', auth('contact')->user()->client->company_id)
->where('status_id', RecurringInvoice::STATUS_ACTIVE)
->where('is_deleted', 0)
->whereNotNull('subscription_id')
->withTrashed()
->count();
if($count == 0)

View File

@ -200,6 +200,7 @@ class CreditController extends BaseController
$credit = $credit->service()
->fillDefaults()
->triggeredActions($request)
->save();
event(new CreditWasCreated($credit, $credit->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
@ -377,7 +378,9 @@ class CreditController extends BaseController
$credit = $this->credit_repository->save($request->all(), $credit);
$credit->service()->deletePdf();
$credit->service()
->triggeredActions($request)
->deletePdf();
event(new CreditWasUpdated($credit, $credit->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));

View File

@ -0,0 +1,29 @@
<?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\Controllers\Gateways;
use App\Http\Controllers\Controller;
use App\Http\Requests\Gateways\GoCardless\IbpRequest;
use App\Models\GatewayType;
class GoCardlessController extends Controller
{
public function ibpRedirect(IbpRequest $request)
{
return $request
->getCompanyGateway()
->driver($request->getClient())
->setPaymentMethod(GatewayType::INSTANT_BANK_PAY)
->processPaymentResponse($request);
}
}

View File

@ -174,12 +174,7 @@ class PreviewController extends BaseController
MultiDB::setDb($company->db);
if($request->input('entity') == 'invoice'){
$repo = new InvoiceRepository();
$entity_obj = InvoiceFactory::create($company->id, auth()->user()->id);
$class = Invoice::class;
}
elseif($request->input('entity') == 'quote'){
if($request->input('entity') == 'quote'){
$repo = new QuoteRepository();
$entity_obj = QuoteFactory::create($company->id, auth()->user()->id);
$class = Quote::class;
@ -195,7 +190,11 @@ class PreviewController extends BaseController
$entity_obj = RecurringInvoiceFactory::create($company->id, auth()->user()->id);
$class = RecurringInvoice::class;
}
else { //assume it is either an invoice or a null object
$repo = new InvoiceRepository();
$entity_obj = InvoiceFactory::create($company->id, auth()->user()->id);
$class = Invoice::class;
}
try {

View File

@ -189,6 +189,9 @@ class BillingPortalPurchase extends Component
$this->coupon = request()->query('coupon');
$this->handleCoupon();
}
elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0){
$this->price = $this->subscription->promo_price;
}
}
/**

View File

@ -141,7 +141,7 @@ class RequiredClientInfo extends Component
$_field = $this->mappings[$field['name']];
if (Str::startsWith($field['name'], 'client_')) {
if (empty($this->contact->client->{$_field}) || is_null($this->contact->client->{$_field})) {
if (empty($this->contact->client->{$_field}) || is_null($this->contact->client->{$_field}) || $this->contact->client->{$_field} = 840) {
$this->show_form = true;
} else {
$this->fields[$index]['filled'] = true;
@ -149,7 +149,7 @@ class RequiredClientInfo extends Component
}
if (Str::startsWith($field['name'], 'contact_')) {
if ((empty($this->contact->{$_field}) || is_null($this->contact->{$_field}))) {
if ((empty($this->contact->{$_field}) || is_null($this->contact->{$_field})) || $this->contact->client->{$_field} = 840) {
$this->show_form = true;
} else {
$this->fields[$index]['filled'] = true;

View File

@ -36,6 +36,7 @@ class TasksTable extends Component
{
$query = Task::query()
->where('company_id', $this->company->id)
->where('is_deleted', false)
->where('client_id', auth('contact')->user()->client->id);
if ($this->company->getSetting('show_all_tasks_client_portal') === 'invoiced') {

View File

@ -84,6 +84,8 @@ class WepaySignup extends Component
public function submit()
{
MultiDB::setDb($this->company->db);
$data = $this->validate($this->rules);
//need to create or get a new WePay CompanyGateway

View File

@ -34,12 +34,12 @@ class CreateAccountRequest extends Request
public function rules()
{
return [
//'email' => 'required|string|email|max:100',
'first_name' => 'string|max:100',
'last_name' => 'string:max:100',
'password' => 'required|string|min:6',
'email' => 'bail|required|email:rfc,dns',
'email' => new NewUniqueUserRule(),
// 'email' => 'bail|required|email:rfc,dns',
// 'email' => new NewUniqueUserRule(),
'email' => ['required', 'email:rfc,dns', new NewUniqueUserRule],
'privacy_policy' => 'required|boolean',
'terms_of_service' => 'required|boolean',
];

View File

@ -28,7 +28,8 @@ class ShowDocumentRequest extends FormRequest
public function authorize()
{
return auth()->user('contact')->client->id == $this->document->documentable_id
|| $this->document->documentable->client_id == auth()->user('contact')->client->id;
|| $this->document->documentable->client_id == auth()->user('contact')->client->id
|| $this->document->company_id == auth()->user('contact')->company->id;
}
/**

View File

@ -0,0 +1,67 @@
<?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\Requests\Gateways\GoCardless;
use App\Models\Client;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\PaymentHash;
use App\Utils\Traits\MakesHash;
use Illuminate\Foundation\Http\FormRequest;
class IbpRequest extends FormRequest
{
use MakesHash;
/**
* 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 [
//
];
}
public function getCompany(): ?Company
{
return Company::where('company_key', $this->company_key)->first();
}
public function getCompanyGateway(): ?CompanyGateway
{
return CompanyGateway::find($this->decodePrimaryKey($this->company_gateway_id));
}
public function getPaymentHash(): ?PaymentHash
{
return PaymentHash::where('hash', $this->hash)->firstOrFail();
}
public function getClient(): ?Client
{
return Client::find($this->getPaymentHash()->data->client_id);
}
}

View File

@ -69,6 +69,7 @@ class ApplyCreditPayment implements ShouldQueue
$this->credit
->service()
->markSent()
->setStatus(Credit::STATUS_APPLIED)
->adjustBalance($this->amount * -1)
->updatePaidToDate($this->amount)
@ -78,6 +79,7 @@ class ApplyCreditPayment implements ShouldQueue
$this->credit
->service()
->markSent()
->setStatus(Credit::STATUS_PARTIAL)
->adjustBalance($this->amount * -1)
->updatePaidToDate($this->amount)

View File

@ -0,0 +1,86 @@
<?php
/**
* Quote Ninja (https://quoteninja.com).
*
* @link https://github.com/quoteninja/quoteninja source repository
*
* @copyright Copyright (c) 2021. Quote Ninja LLC (https://quoteninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Listeners\Quote;
use App\Jobs\Mail\NinjaMailer;
use App\Jobs\Mail\NinjaMailerJob;
use App\Jobs\Mail\NinjaMailerObject;
use App\Libraries\MultiDB;
use App\Mail\Admin\EntityCreatedObject;
use App\Mail\Admin\QuoteApprovedObject;
use App\Notifications\Admin\EntitySentNotification;
use App\Utils\Traits\Notifications\UserNotifies;
use Illuminate\Contracts\Queue\ShouldQueue;
class QuoteApprovedNotification implements ShouldQueue
{
use UserNotifies;
public function __construct()
{
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
MultiDB::setDb($event->company->db);
$first_notification_sent = true;
$quote = $event->quote;
$nmo = new NinjaMailerObject;
$nmo->mailable = new NinjaMailer( (new QuoteApprovedObject($quote, $event->company))->build() );
$nmo->company = $quote->company;
$nmo->settings = $quote->company->settings;
/* We loop through each user and determine whether they need to be notified */
foreach ($event->company->company_users as $company_user) {
/* The User */
$user = $company_user->user;
if(!$user)
continue;
/* This is only here to handle the alternate message channels - ie Slack */
// $notification = new EntitySentNotification($event->invitation, 'quote');
/* Returns an array of notification methods */
$methods = $this->findUserNotificationTypes($quote->invitations()->first(), $company_user, 'quote', ['all_notifications', 'quote_approved', 'quote_approved_all']);
/* If one of the methods is email then we fire the EntitySentMailer */
if (($key = array_search('mail', $methods)) !== false) {
unset($methods[$key]);
$nmo->to_user = $user;
NinjaMailerJob::dispatch($nmo);
/* This prevents more than one notification being sent */
$first_notification_sent = false;
}
/* Override the methods in the Notification Class */
// $notification->method = $methods;
// Notify on the alternate channels
// $user->notify($notification);
}
}
}

View File

@ -69,7 +69,7 @@ class ClientPaymentFailureObject
/* Set customized translations _NOW_ */
$t->replace(Ninja::transformTranslations($this->company->settings));
$this->invoices = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->get();
$this->invoices = Invoice::withTrashed()->whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->get();
$mail_obj = new stdClass;
$mail_obj->amount = $this->getAmount();
@ -101,8 +101,13 @@ class ClientPaymentFailureObject
private function getData()
{
$invitation = $this->invoices->first()->invitations->first();
if(!$invitation)
throw new \Exception('Unable to find invitation for reference');
$signature = $this->client->getSetting('email_signature');
$html_variables = (new HtmlEngine($this->invoices->first()->invitations->first()))->makeValues();
$html_variables = (new HtmlEngine($invitation))->makeValues();
$signature = str_replace(array_keys($html_variables), array_values($html_variables), $signature);
$data = [

View File

@ -0,0 +1,103 @@
<?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\Mail\Admin;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Models\Quote;
use App\Utils\Ninja;
use App\Utils\Number;
use Illuminate\Support\Facades\App;
use stdClass;
class QuoteApprovedObject
{
public $quote;
public $company;
public $settings;
public function __construct(Quote $quote, Company $company)
{
$this->quote = $quote;
$this->company = $company;
}
public function build()
{
MultiDB::setDb($this->company->db);
if(!$this->quote)
return;
App::forgetInstance('translator');
/* Init a new copy of the translator*/
$t = app('translator');
/* Set the locale*/
App::setLocale($this->company->getLocale());
/* Set customized translations _NOW_ */
$t->replace(Ninja::transformTranslations($this->company->settings));
$mail_obj = new stdClass;
$mail_obj->amount = $this->getAmount();
$mail_obj->subject = $this->getSubject();
$mail_obj->data = $this->getData();
$mail_obj->markdown = 'email.admin.generic';
$mail_obj->tag = $this->company->company_key;
return $mail_obj;
}
private function getAmount()
{
return Number::formatMoney($this->quote->amount, $this->quote->client);
}
private function getSubject()
{
return
ctrans(
"texts.notification_quote_approved_subject",
[
'client' => $this->quote->client->present()->name(),
'invoice' => $this->quote->number,
]
);
}
private function getData()
{
$settings = $this->quote->client->getMergedSettings();
$data = [
'title' => $this->getSubject(),
'message' => ctrans(
"texts.notification_quote_approved",
[
'amount' => $this->getAmount(),
'client' => $this->quote->client->present()->name(),
'invoice' => $this->quote->number,
]
),
'url' => $this->quote->invitations->first()->getAdminLink(),
'button' => ctrans("texts.view_quote"),
'signature' => $settings->email_signature,
'logo' => $this->company->present()->logo(),
'settings' => $settings,
'whitelabel' => $this->company->account->isPaid() ? true : false,
];
return $data;
}
}

View File

@ -136,4 +136,4 @@ class InvoiceEmailEngine extends BaseEmailEngine
return $this;
}
}
}

View File

@ -18,6 +18,7 @@ use App\Models\ClientContact;
use App\Models\User;
use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
use App\Utils\HtmlEngine;
use App\Utils\Ninja;
use App\Utils\TemplateEngine;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
@ -114,6 +115,10 @@ class TemplateEmail extends Mailable
$message->invitation = $this->invitation;
});
/*In the hosted platform we need to slow things down a little for Storage to catch up.*/
if(Ninja::isHosted())
sleep(1);
foreach ($this->build_email->getAttachments() as $file) {
if(is_string($file))

View File

@ -195,6 +195,8 @@ class BaseModel extends Model
// Remove any runs of periods (thanks falstro!)
$formatted_number = mb_ereg_replace("([\.]{2,})", '', $formatted_number);
$formatted_number = str_replace(" ", "_", $formatted_number);
return $formatted_number;
}

View File

@ -159,7 +159,8 @@ class Gateway extends StaticModel
return [
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']], // GoCardless
GatewayType::DIRECT_DEBIT => ['refund' => false, 'token_billing' => true, 'webhooks' => [' ']],
GatewayType::SEPA => ['refund' => false, 'token_billing' => true, 'webhooks' => [' ']]
GatewayType::SEPA => ['refund' => false, 'token_billing' => true, 'webhooks' => [' ']],
GatewayType::INSTANT_BANK_PAY => ['refund' => false, 'token_billing' => true, 'webhooks' => [' ']],
];
break;
case 58:

View File

@ -37,6 +37,7 @@ class GatewayType extends StaticModel
const DIRECT_DEBIT = 18;
const ACSS = 19;
const BECS = 20;
const INSTANT_BANK_PAY = 21;
public function gateway()
{
@ -89,6 +90,8 @@ class GatewayType extends StaticModel
return ctrans('texts.acss');
case self::DIRECT_DEBIT:
return ctrans('texts.payment_type_direct_debit');
case self::INSTANT_BANK_PAY:
return ctrans('texts.payment_type_instant_bank_pay');
default:
return 'Undefined.';
break;

View File

@ -53,6 +53,7 @@ class PaymentType extends StaticModel
const DIRECT_DEBIT = 42;
const BECS = 43;
const ACSS = 44;
const INSTANT_BANK_PAY = 45;
public static function parseCardType($cardName)
{

View File

@ -222,6 +222,9 @@ class RecurringInvoice extends BaseModel
return null;
}
nlog("frequency = $this->frequency_id");
nlog("frequency = $this->next_send_date");
$offset = $this->client->timezone_offset();
/*

View File

@ -404,7 +404,7 @@ class BaseDriver extends AbstractPaymentDriver
throw new PaymentFailed($error, $e->getCode());
}
public function sendFailureMail(string $error)
public function sendFailureMail($error = '')
{
if (!is_null($this->payment_hash)) {

View File

@ -0,0 +1,182 @@
<?php
namespace App\PaymentDrivers\GoCardless;
use App\Exceptions\PaymentFailed;
use App\Jobs\Mail\PaymentFailureMailer;
use App\Jobs\Util\SystemLogger;
use App\Models\GatewayType;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\Common\MethodInterface;
use App\PaymentDrivers\GoCardlessPaymentDriver;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class InstantBankPay implements MethodInterface
{
protected GoCardlessPaymentDriver $go_cardless;
public function __construct(GoCardlessPaymentDriver $go_cardless)
{
$this->go_cardless = $go_cardless;
$this->go_cardless->init();
}
/**
* Authorization page for Instant Bank Pay.
*
* @param array $data
* @return RedirectResponse
* @throws BindingResolutionException
*/
public function authorizeView(array $data): RedirectResponse
{
return redirect()->back();
}
/**
* Handle authorization for Instant Bank Pay.
*
* @param array $data
* @return RedirectResponse
* @throws BindingResolutionException
*/
public function authorizeResponse(Request $request): RedirectResponse
{
return redirect()->back();
}
public function paymentView(array $data)
{
try {
$billing_request = $this->go_cardless->gateway->billingRequests()->create([
'params' => [
'payment_request' => [
'description' => ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number'),
'amount' => (string) $data['amount_with_fee'] * 100,
'currency' => $this->go_cardless->client->getCurrencyCode(),
],
]
]);
$billing_request_flow = $this->go_cardless->gateway->billingRequestFlows()->create([
'params' => [
'redirect_uri' => route('gocardless.ibp_redirect', [
'company_key' => $this->go_cardless->company_gateway->company->company_key,
'company_gateway_id' => $this->go_cardless->company_gateway->hashed_id,
'hash' => $this->go_cardless->payment_hash->hash,
]),
'links' => [
'billing_request' => $billing_request->id,
]
],
]);
$this->go_cardless->payment_hash
->withData('client_id', $this->go_cardless->client->id)
->withData('billing_request', $billing_request->id)
->withData('billing_request_flow', $billing_request_flow->id);
return redirect(
$billing_request_flow->authorisation_url
);
} catch (\Exception $exception) {
throw $exception;
}
}
public function paymentResponse($request)
{
$this->go_cardless->setPaymentHash(
$request->getPaymentHash()
);
try {
$billing_request = $this->go_cardless->gateway->billingRequests()->get(
$this->go_cardless->payment_hash->data->billing_request
);
$payment = $this->go_cardless->gateway->payments()->get(
$billing_request->payment_request->links->payment
);
if ($billing_request->status === 'fulfilled') {
return $this->processSuccessfulPayment($payment);
}
return $this->processUnsuccessfulPayment($payment);
} catch (\Exception $exception) {
throw new PaymentFailed(
$exception->getMessage(),
$exception->getCode()
);
}
}
/**
* Handle pending payments for Instant Bank Transfer.
*
* @param ResourcesPayment $payment
* @param array $data
* @return RedirectResponse
*/
public function processSuccessfulPayment(\GoCardlessPro\Resources\Payment $payment, array $data = [])
{
$data = [
'payment_method' => $payment->links->mandate,
'payment_type' => PaymentType::INSTANT_BANK_PAY,
'amount' => $this->go_cardless->payment_hash->data->amount_with_fee,
'transaction_reference' => $payment->id,
'gateway_type_id' => GatewayType::INSTANT_BANK_PAY,
];
$payment = $this->go_cardless->createPayment($data, Payment::STATUS_COMPLETED);
SystemLogger::dispatch(
['response' => $payment, 'data' => $data],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_GOCARDLESS,
$this->go_cardless->client,
$this->go_cardless->client->company,
);
return redirect()->route('client.payments.show', ['payment' => $this->go_cardless->encodePrimaryKey($payment->id)]);
}
/**
* Process unsuccessful payments for Direct Debit.
*
* @param ResourcesPayment $payment
* @return never
*/
public function processUnsuccessfulPayment(\GoCardlessPro\Resources\Payment $payment)
{
PaymentFailureMailer::dispatch($this->go_cardless->client, $payment->status, $this->go_cardless->client->company, $this->go_cardless->payment_hash->data->amount_with_fee);
PaymentFailureMailer::dispatch(
$this->go_cardless->client,
$payment,
$this->go_cardless->client->company,
$payment->amount
);
$message = [
'server_response' => $payment,
'data' => $this->go_cardless->payment_hash->data,
];
SystemLogger::dispatch(
$message,
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_FAILURE,
SystemLog::TYPE_GOCARDLESS,
$this->go_cardless->client,
$this->go_cardless->client->company,
);
}
}

View File

@ -40,6 +40,7 @@ class GoCardlessPaymentDriver extends BaseDriver
GatewayType::BANK_TRANSFER => \App\PaymentDrivers\GoCardless\ACH::class,
GatewayType::DIRECT_DEBIT => \App\PaymentDrivers\GoCardless\DirectDebit::class,
GatewayType::SEPA => \App\PaymentDrivers\GoCardless\SEPA::class,
GatewayType::INSTANT_BANK_PAY => \App\PaymentDrivers\GoCardless\InstantBankPay::class,
];
const SYSTEM_LOG_TYPE = SystemLog::TYPE_GOCARDLESS;
@ -77,6 +78,10 @@ class GoCardlessPaymentDriver extends BaseDriver
$types[] = GatewayType::SEPA;
}
if ($this->client->currency()->code === 'GBP') {
$types[] = GatewayType::INSTANT_BANK_PAY;
}
return $types;
}

View File

@ -86,6 +86,7 @@ class Bancontact implements MethodInterface
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
'metadata' => [
'client_id' => $this->mollie->client->hashed_id,
'hash' => $this->mollie->payment_hash->hash
],
]);

View File

@ -89,6 +89,7 @@ class BankTransfer implements MethodInterface
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
'metadata' => [
'client_id' => $this->mollie->client->hashed_id,
'hash' => $this->mollie->payment_hash->hash
],
]);

View File

@ -86,6 +86,7 @@ class IDEAL implements MethodInterface
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
'metadata' => [
'client_id' => $this->mollie->client->hashed_id,
'hash' => $this->mollie->payment_hash->hash
],
]);

View File

@ -86,6 +86,7 @@ class KBC implements MethodInterface
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
'metadata' => [
'client_id' => $this->mollie->client->hashed_id,
'hash' => $this->mollie->payment_hash->hash
],
]);

View File

@ -93,7 +93,7 @@ class PayPalExpressPaymentDriver extends BaseDriver
return $response->redirect();
}
$this->sendFailureMail($response->getMessage());
$this->sendFailureMail($response->getMessage() ?: '');
$message = [
'server_response' => $response->getMessage(),
@ -151,7 +151,7 @@ class PayPalExpressPaymentDriver extends BaseDriver
$data = $response->getData();
$this->sendFailureMail($response->getMessage());
$this->sendFailureMail($response->getMessage() ?: '');
$message = [
'server_response' => $data['L_LONGMESSAGE0'],

View File

@ -99,4 +99,9 @@ class RazorpayPaymentDriver extends BaseDriver
{
return \number_format((float) $amount * 100, 0, '.', '');
}
public function processWebhookRequest(): void
{
//
}
}

View File

@ -524,14 +524,25 @@ class StripePaymentDriver extends BaseDriver
if ($request->type === 'charge.succeeded' || $request->type === 'payment_intent.succeeded') {
foreach ($request->data as $transaction) {
$payment = Payment::query()
->where('transaction_reference', $transaction['payment_intent'])
if(array_key_exists('payment_intent', $transaction))
{
$payment = Payment::query()
->where('company_id', $request->getCompany()->id)
->where(function ($query) use ($transaction) {
$query->where('transaction_reference', $transaction['payment_intent'])
->orWhere('transaction_reference', $transaction['id']);
})
->first();
}
else
{
$payment = Payment::query()
->where('company_id', $request->getCompany()->id)
->where('transaction_reference', $transaction['id'])
->first();
}
if ($payment) {
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();

View File

@ -208,8 +208,8 @@ class WePayPaymentDriver extends BaseDriver
return 'Processed successfully';
} elseif ($objectType == 'account') {
if ($accountId != $objectId) {
throw new \Exception('Unknown account');
if ($accountId !== $objectId) {
throw new \Exception('Unknown account ' . $accountId . ' does not equal '.$objectId);
}
$wepayAccount = $this->wepay->request('account', array(

View File

@ -171,6 +171,7 @@ use App\Listeners\Payment\PaymentEmailedActivity;
use App\Listeners\Payment\PaymentNotification;
use App\Listeners\Payment\PaymentRestoredActivity;
use App\Listeners\Quote\QuoteApprovedActivity;
use App\Listeners\Quote\QuoteApprovedNotification;
use App\Listeners\Quote\QuoteApprovedWebhook;
use App\Listeners\Quote\QuoteArchivedActivity;
use App\Listeners\Quote\QuoteCreatedNotification;
@ -437,6 +438,7 @@ class EventServiceProvider extends ServiceProvider
ReachWorkflowSettings::class,
QuoteApprovedActivity::class,
QuoteApprovedWebhook::class,
QuoteApprovedNotification::class,
],
QuoteWasCreated::class => [
CreatedQuoteActivity::class,

View File

@ -22,7 +22,9 @@ use Illuminate\Support\Str;
*/
class ClientContactRepository extends BaseRepository
{
public $is_primary;
private bool $is_primary = true;
private bool $set_send_email_on_contact = false;
public function save(array $data, Client $client) : void
{
@ -36,13 +38,20 @@ class ClientContactRepository extends BaseRepository
ClientContact::destroy($contact);
});
$this->is_primary = true;
/* Ensure send_email always exists in at least one contact */
if(!$contacts->contains('send_email', true))
$this->set_send_email_on_contact = true;
/* Set first record to primary - always */
$contacts = $contacts->sortByDesc('is_primary')->map(function ($contact) {
$contact['is_primary'] = $this->is_primary;
$this->is_primary = false;
if($this->set_send_email_on_contact){
$contact['send_email'] = true;
$this->set_send_email_on_contact = false;
}
return $contact;
});

View File

@ -129,7 +129,7 @@ class PaymentRepository extends BaseRepository {
//todo optimize this into a single query
foreach ($data['invoices'] as $paid_invoice) {
$invoice = Invoice::whereId($paid_invoice['invoice_id'])->first();
$invoice = Invoice::withTrashed()->whereId($paid_invoice['invoice_id'])->first();
if ($invoice) {
$invoice = $invoice->service()

View File

@ -14,6 +14,7 @@ namespace App\Services\Credit;
use App\Jobs\Util\UnlinkFile;
use App\Models\Credit;
use App\Services\Credit\CreateInvitations;
use App\Services\Credit\TriggeredActions;
use App\Utils\Traits\MakesHash;
class CreditService
@ -150,6 +151,13 @@ class CreditService
return $this;
}
public function triggeredActions($request)
{
$this->invoice = (new TriggeredActions($this->credit, $request))->run();
return $this;
}
/**
* Saves the credit.
* @return Credit object

View File

@ -0,0 +1,76 @@
<?php
/**
* Credit Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Credit Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\Credit;
use App\Events\Credit\CreditWasEmailed;
use App\Jobs\Entity\EmailEntity;
use App\Models\Credit;
use App\Services\AbstractService;
use App\Utils\Ninja;
use App\Utils\Traits\GeneratesCounter;
use Illuminate\Http\Request;
class TriggeredActions extends AbstractService
{
use GeneratesCounter;
private $request;
private $credit;
public function __construct(Credit $credit, Request $request)
{
$this->request = $request;
$this->credit = $credit;
}
public function run()
{
// if ($this->request->has('auto_bill') && $this->request->input('auto_bill') == 'true') {
// $this->credit = $this->credit->service()->autoBill()->save();
// }
// if ($this->request->has('paid') && $this->request->input('paid') == 'true') {
// $this->credit = $this->credit->service()->markPaid()->save();
// }
// if ($this->request->has('amount_paid') && is_numeric($this->request->input('amount_paid')) ) {
// $this->credit = $this->credit->service()->applyPaymentAmount($this->request->input('amount_paid'))->save();
// }
if ($this->request->has('send_email') && $this->request->input('send_email') == 'true') {
$this->sendEmail();
}
if ($this->request->has('mark_sent') && $this->request->input('mark_sent') == 'true') {
$this->credit = $this->credit->service()->markSent()->save();
}
return $this->credit;
}
private function sendEmail()
{
$reminder_template = $this->credit->calculateTemplate('credit');
$this->credit->invitations->load('contact.client.country', 'credit.client.country', 'credit.company')->each(function ($invitation) use ($reminder_template) {
EmailEntity::dispatch($invitation, $this->credit->company, $reminder_template);
});
if ($this->credit->invitations->count() > 0) {
event(new CreditWasEmailed($this->credit->invitations->first(), $this->credit->company, Ninja::eventVars(), 'credit'));
}
}
}

View File

@ -47,7 +47,7 @@ class ApplyPayment extends AbstractService
$amount_paid = $this->payment_amount * -1;
$this->invoice->service()->clearPartial()->setDueDate()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($amount_paid);
$this->invoice->service()->clearPartial()->setDueDate()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($amount_paid)->save();
}
elseif ($this->invoice->partial > 0 && $this->invoice->partial > $this->payment_amount)
@ -56,7 +56,7 @@ class ApplyPayment extends AbstractService
$amount_paid = $this->payment_amount * -1;
$this->invoice->service()->updatePartial($amount_paid)->updateBalance($amount_paid);
$this->invoice->service()->updatePartial($amount_paid)->updateBalance($amount_paid)->save();
}
elseif ($this->invoice->partial > 0 && $this->invoice->partial < $this->payment_amount)
@ -65,7 +65,7 @@ class ApplyPayment extends AbstractService
$amount_paid = $this->payment_amount * -1;
$this->invoice->service()->clearPartial()->setDueDate()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($amount_paid);
$this->invoice->service()->clearPartial()->setDueDate()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($amount_paid)->save();
}
@ -76,7 +76,7 @@ class ApplyPayment extends AbstractService
{
$amount_paid = $this->payment_amount * -1;
$this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PAID)->updateBalance($amount_paid);
$this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PAID)->updateBalance($amount_paid)->save();
}
elseif ($this->payment_amount < $this->invoice->balance)
@ -85,7 +85,7 @@ class ApplyPayment extends AbstractService
$amount_paid = $this->payment_amount * -1;
$this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($amount_paid);
$this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($amount_paid)->save();
}
@ -95,7 +95,7 @@ class ApplyPayment extends AbstractService
$amount_paid = $this->invoice->balance * -1;
$this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PAID)->updateBalance($amount_paid);
$this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PAID)->updateBalance($amount_paid)->save();
}
}

View File

@ -136,7 +136,17 @@ class InvoiceService
*/
public function updateBalance($balance_adjustment, bool $is_draft = false)
{
$this->invoice = (new UpdateBalance($this->invoice, $balance_adjustment, $is_draft))->run();
// $this->invoice = (new UpdateBalance($this->invoice, $balance_adjustment, $is_draft))->run();
if ($this->invoice->is_deleted) {
return $this;
}
$this->invoice->balance += $balance_adjustment;
if ($this->invoice->balance == 0 && !$is_draft) {
$this->invoice->status_id = Invoice::STATUS_PAID;
}
if ((int)$this->invoice->balance == 0) {
$this->invoice->next_send_date = null;

View File

@ -82,10 +82,15 @@ class UpdateInvoicePayment
->updateBalance($paid_amount * -1)
->updatePaidToDate($paid_amount)
->updateStatus()
->deletePdf()
->workFlow()
->save();
$invoice->refresh();
$invoice->service()
->deletePdf()
->workFlow()
->save();
event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
});

View File

@ -75,9 +75,9 @@ class SubscriptionService
$recurring_invoice = $this->convertInvoiceToRecurring($payment_hash->payment->client_id);
$recurring_invoice_repo = new RecurringInvoiceRepository();
$recurring_invoice->next_send_date = now();
$recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice);
$recurring_invoice->next_send_date = $recurring_invoice->nextSendDate();
// $recurring_invoice->next_send_date = now()->format('Y-m-d');
// $recurring_invoice->next_send_date = $recurring_invoice->nextSendDate();
$recurring_invoice->auto_bill = $this->subscription->auto_bill;
/* Start the recurring service */
@ -161,6 +161,11 @@ class SubscriptionService
$recurring_invoice->discount = $this->subscription->promo_discount;
$recurring_invoice->is_amount_discount = $this->subscription->is_amount_discount;
}
elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0) {
$recurring_invoice->discount = $this->subscription->promo_discount;
$recurring_invoice->is_amount_discount = $this->subscription->is_amount_discount;
}
$recurring_invoice = $recurring_invoice_repo->save($data, $recurring_invoice);
@ -594,7 +599,7 @@ class SubscriptionService
$recurring_invoice = $this->convertInvoiceToRecurring($old_recurring_invoice->client_id);
$recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice);
$recurring_invoice->next_send_date = now();
$recurring_invoice->next_send_date = now()->format('Y-m-d');
$recurring_invoice->next_send_date = $recurring_invoice->nextSendDate();
/* Start the recurring service */
@ -693,6 +698,11 @@ class SubscriptionService
$invoice->discount = $this->subscription->promo_discount;
$invoice->is_amount_discount = $this->subscription->is_amount_discount;
}
elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0) {
$invoice->discount = $this->subscription->promo_discount;
$invoice->is_amount_discount = $this->subscription->is_amount_discount;
}
return $invoice_repo->save($data, $invoice);
@ -722,7 +732,9 @@ class SubscriptionService
$recurring_invoice->auto_bill = $client->getSetting('auto_bill');
$recurring_invoice->auto_bill_enabled = $this->setAutoBillFlag($recurring_invoice->auto_bill);
$recurring_invoice->due_date_days = 'terms';
$recurring_invoice->next_send_date = now()->format('Y-m-d');
$recurring_invoice->next_send_date = $recurring_invoice->nextSendDate();
return $recurring_invoice;
}
@ -1027,7 +1039,7 @@ class SubscriptionService
'subscription' => $this->subscription->hashed_id,
'recurring_invoice' => $recurring_invoice_hashed_id,
'client' => $invoice->client->hashed_id,
'contact' => $invoice->client->primary_contact()->first() ? $invoice->client->primary_contact()->first(): $invoice->client->contacts->first(),
'contact' => $invoice->client->primary_contact()->first() ? $invoice->client->primary_contact()->first()->hashed_id: $invoice->client->contacts->first()->hashed_id,
'invoice' => $invoice->hashed_id,
];

View File

@ -257,7 +257,7 @@ class TemplateEngine
]);
if($this->entity == 'invoice')
if(!$this->entity || $this->entity == 'invoice')
{
$this->entity_obj = Invoice::factory()->create([
'user_id' => auth()->user()->id,

View File

@ -150,4 +150,4 @@
},
"minimum-stability": "dev",
"prefer-stable": true
}
}

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.3.31',
'app_tag' => '5.3.31',
'app_version' => '5.3.32',
'app_tag' => '5.3.32',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', ''),

View File

@ -0,0 +1,26 @@
<?php
use App\Models\GatewayType;
use App\Models\PaymentType;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddInstantBankTransfer extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
$type = new PaymentType();
$type->id = PaymentType::INSTANT_BANK_PAY;
$type->name = 'Instant Bank Pay';
$type->gateway_type_id = GatewayType::INSTANT_BANK_PAY;
$type->save();
}
}

View File

@ -6,7 +6,7 @@ const RESOURCES = {
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
"/": "1c5f475f85b7fcd619029ee7f07d9d02",
"/": "6310be19342ca9ed6920572bcc23151f",
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
"favicon.ico": "51636d3a390451561744c42188ccd628",
"version.json": "9c7b0edc83733da56c726678aacd9fd3",
@ -34,7 +34,7 @@ const RESOURCES = {
"assets/assets/images/google_logo.png": "0f118259ce403274f407f5e982e681c3",
"assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219",
"assets/AssetManifest.json": "38d9aea341601f3a5c6fa7b5a1216ea5",
"main.dart.js": "97d45d9acc730c1517f80cecf1a90511"
"main.dart.js": "d57b5bf95106fcc642bac2ab5bc52fc3"
};
// The application shell files that are downloaded before a service worker can

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
(()=>{var e,t={2623:(e,t,r)=>{"use strict";e.exports=r(4666)},1886:(e,t)=>{"use strict";const r=e=>e.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/'/g,"&#39;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),n=e=>e.replace(/&gt;/g,">").replace(/&lt;/g,"<").replace(/&#0?39;/g,"'").replace(/&quot;/g,'"').replace(/&amp;/g,"&");t.T=(e,...t)=>{if("string"==typeof e)return r(e);let n=e[0];for(const[o,a]of t.entries())n=n+r(String(a))+e[o+1];return n}},7636:(e,t,r)=>{"use strict";r.r(t),r.d(t,{default:()=>s});var n=r(1886);var o=r(2623);const a=e=>e.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/'/g,"&#39;").replace(/</g,"&lt;").replace(/>/g,"&gt;");const i=new Set(o);function c({name:e="div",attributes:t={},html:r="",text:o}={}){if(r&&o)throw new Error("The `html` and `text` options are mutually exclusive");const c=o?function(e,...t){if("string"==typeof e)return a(e);let r=e[0];for(const[n,o]of t.entries())r=r+a(String(o))+e[n+1];return r}(o):r;let l=`<${e}${function(e){const t=[];for(let[r,o]of Object.entries(e)){if(!1===o)continue;Array.isArray(o)&&(o=o.join(" "));let e=(0,n.T)(r);!0!==o&&(e+=`="${(0,n.T)(String(o))}"`),t.push(e)}return t.length>0?" "+t.join(" "):""}(t)}>`;return i.has(e)||(l+=`${c}</${e}>`),l}const l=(e,t)=>c({name:"a",attributes:{href:"",...t.attributes,href:e},text:void 0===t.value?e:void 0,html:void 0===t.value?void 0:"function"==typeof t.value?t.value(e):t.value});function s(e,t){if("string"===(t={attributes:{},type:"string",...t}).type)return((e,t)=>e.replace(/((?<!\+)https?:\/\/(?:www\.)?(?:[-\w.]+?[.@][a-zA-Z\d]{2,}|localhost)(?:[-\w.:%+~#*$!?&/=@]*?(?:,(?!\s))*?)*)/g,(e=>l(e,t))))(e,t);if("dom"===t.type)return((e,t)=>{const r=document.createDocumentFragment();for(const[o,a]of Object.entries(e.split(/((?<!\+)https?:\/\/(?:www\.)?(?:[-\w.]+?[.@][a-zA-Z\d]{2,}|localhost)(?:[-\w.:%+~#*$!?&/=@]*?(?:,(?!\s))*?)*)/g)))o%2?r.append((n=l(a,t),document.createRange().createContextualFragment(n))):a.length>0&&r.append(a);var n;return r})(e,t);throw new TypeError("The type option must be either `dom` or `string`")}},4666:e=>{"use strict";e.exports=JSON.parse('["area","base","br","col","embed","hr","img","input","link","menuitem","meta","param","source","track","wbr"]')}},r={};function n(e){var o=r[e];if(void 0!==o)return o.exports;var a=r[e]={exports:{}};return t[e](a,a.exports,n),a.exports}n.d=(e,t)=>{for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},e=n(7636),document.querySelectorAll("[data-ref=entity-terms]").forEach((function(t){t.innerHTML=e(t.innerText,{attributes:{target:"_blank",class:"text-primary"}})}))})();
(()=>{var e,t={8945:(e,t,r)=>{"use strict";const a=r(920),n=r(3523),s=r(2263),o=new Set(n);e.exports=e=>{if((e=Object.assign({name:"div",attributes:{},html:""},e)).html&&e.text)throw new Error("The `html` and `text` options are mutually exclusive");const t=e.text?s.escape(e.text):e.html;let r=`<${e.name}${a(e.attributes)}>`;return o.has(e.name)||(r+=`${t}</${e.name}>`),r}},3523:(e,t,r)=>{"use strict";e.exports=r(8346)},2263:(e,t)=>{"use strict";t.escape=e=>e.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/'/g,"&#39;").replace(/</g,"&lt;").replace(/>/g,"&gt;"),t.unescape=e=>e.replace(/&gt;/g,">").replace(/&lt;/g,"<").replace(/&#39;/g,"'").replace(/&quot;/g,'"').replace(/&amp;/g,"&"),t.escapeTag=function(e){let r=e[0];for(let a=1;a<arguments.length;a++)r=r+t.escape(arguments[a])+e[a];return r},t.unescapeTag=function(e){let r=e[0];for(let a=1;a<arguments.length;a++)r=r+t.unescape(arguments[a])+e[a];return r}},1881:(e,t,r)=>{"use strict";const a=r(8945),n=(e,t)=>a({name:"a",attributes:{href:"",...t.attributes,href:e},text:void 0===t.value?e:void 0,html:void 0===t.value?void 0:"function"==typeof t.value?t.value(e):t.value});e.exports=(e,t)=>{if("string"===(t={attributes:{},type:"string",...t}).type)return((e,t)=>e.replace(/((?<!\+)(?:https?(?::\/\/))(?:www\.)?(?:[a-zA-Z\d-_.]+(?:(?:\.|@)[a-zA-Z\d]{2,})|localhost)(?:(?:[-a-zA-Z\d:%_+.~#*$!?&//=@]*)(?:[,](?![\s]))*)*)/g,(e=>n(e,t))))(e,t);if("dom"===t.type)return((e,t)=>{const r=document.createDocumentFragment();for(const[s,o]of Object.entries(e.split(/((?<!\+)(?:https?(?::\/\/))(?:www\.)?(?:[a-zA-Z\d-_.]+(?:(?:\.|@)[a-zA-Z\d]{2,})|localhost)(?:(?:[-a-zA-Z\d:%_+.~#*$!?&//=@]*)(?:[,](?![\s]))*)*)/g)))s%2?r.append((a=n(o,t),document.createRange().createContextualFragment(a))):o.length>0&&r.append(o);var a;return r})(e,t);throw new Error("The type option must be either `dom` or `string`")}},920:(e,t,r)=>{"use strict";const a=r(2263);e.exports=e=>{const t=[];for(const r of Object.keys(e)){let n=e[r];if(!1===n)continue;Array.isArray(n)&&(n=n.join(" "));let s=a.escape(r);!0!==n&&(s+=`="${a.escape(String(n))}"`),t.push(s)}return t.length>0?" "+t.join(" "):""}},8346:e=>{"use strict";e.exports=JSON.parse('["area","base","br","col","embed","hr","img","input","link","menuitem","meta","param","source","track","wbr"]')}},r={};function a(e){var n=r[e];if(void 0!==n)return n.exports;var s=r[e]={exports:{}};return t[e](s,s.exports,a),s.exports}e=a(1881),document.querySelectorAll("[data-ref=entity-terms]").forEach((function(t){t.innerHTML=e(t.innerText,{attributes:{target:"_blank",class:"text-primary"}})}))})();

View File

@ -1,2 +1,2 @@
/*! For license information please see razorpay-aio.js.LICENSE.txt */
(()=>{var e,n=JSON.parse(null===(e=document.querySelector("meta[name=razorpay-options]"))||void 0===e?void 0:e.content);n.handler=function(e){document.getElementById("razorpay_payment_id").value=e.razorpay_payment_id,document.getElementById("razorpay_signature").value=e.razorpay_signature,document.getElementById("server-response").submit()};var t=new Razorpay(n);document.getElementById("pay-now").onclick=function(e){e.target.parentElement.disabled=!0,t.open()}})();
(()=>{var e,n=JSON.parse(null===(e=document.querySelector("meta[name=razorpay-options]"))||void 0===e?void 0:e.content);n.handler=function(e){document.getElementById("razorpay_payment_id").value=e.razorpay_payment_id,document.getElementById("razorpay_signature").value=e.razorpay_signature,document.getElementById("server-response").submit()},n.modal={ondismiss:function(){o.disabled=!1}};var a=new Razorpay(n),o=document.getElementById("pay-now");o.onclick=function(e){o.disabled=!0,a.open()}})();

File diff suppressed because one or more lines are too long

70865
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

70459
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

68185
public/main.html.dart.js vendored

File diff suppressed because one or more lines are too long

View File

@ -360012,10 +360012,12 @@
t5 = "";
C.JSArray_methods.addAll$1(t4, H.setRuntimeTypeInfo([T.Expanded$(new D.AppButton(_null, C.IconData_57648_MaterialIcons_null_false, t5, new V.DocumentGrid_build_closure(this), _null, _null), 1), new T.SizedBox(14, _null, _null, _null)], t2));
}
t1 = J.$index$asx($.LocalizationsProvider__localizedValues.$index(0, t1.localeCode), "upload_file");
if (t1 == null)
t1 = "";
t4.push(T.Expanded$(new D.AppButton(_null, C.IconData_58178_MaterialIcons_null_false, t1, new V.DocumentGrid_build_closure0(this), _null, _null), 1));
if (!D.isMacOS() && !D.isLinux()) {
t1 = J.$index$asx($.LocalizationsProvider__localizedValues.$index(0, t1.localeCode), "upload_file");
if (t1 == null)
t1 = "";
t4.push(T.Expanded$(new D.AppButton(_null, C.IconData_58178_MaterialIcons_null_false, t1, new V.DocumentGrid_build_closure0(this), _null, _null), 1));
}
t3.push(new T.Padding(C.EdgeInsets_16_0_16_16, T.Row$(t4, C.CrossAxisAlignment_2, C.MainAxisAlignment_0, C.MainAxisSize_1, _null), _null));
} else
t3.push(new T.Padding(new V.EdgeInsets(0, 30, 0, 30), T.Center$(L.Text$(t1.get$requiresAnEnterprisePlan(), _null, _null, _null, _null, _null, A.TextStyle$(_null, _null, C.MaterialColor_Map_HFpTk_4288585374, _null, _null, _null, _null, _null, _null, _null, _null, 18, _null, _null, _null, _null, true, _null, _null, _null, _null, _null, _null, _null), _null, _null, _null), _null, _null), _null));
@ -369199,7 +369201,7 @@
};
Y._LoginState_build_closure16.prototype = {
call$0: function() {
T.launch("https://status.invoiceninja.com", null, false);
T.launch("https://status.invoiceninja.com/", null, false);
},
$signature: 1
};
@ -384888,7 +384890,7 @@
$.WidgetsBinding__instance.SchedulerBinding__postFrameCallbacks.push(new E._InvoiceEditItemsDesktopState__onFocusChange_closure());
},
build$1: function(_, context) {
var t4, company, invoice, t5, t6, client, t7, precision, lineItems, t8, includedLineItems, productState, productIds, hasTax1, hasTax2, hasTax3, customField1, customField2, customField3, customField4, lastIndex, tableFontColor, tableHeaderColor, t9, t10, t11, index, t12, t13, t14, t15, _this = this, _null = null,
var t4, company, invoice, t5, t6, client, t7, precision, lineItems, t8, includedLineItems, productState, productIds, hasTax1, hasTax2, hasTax3, customField1, customField2, customField3, customField4, tableFontColor, tableHeaderColor, lastIndex, t9, t10, t11, index, t12, t13, t14, t15, _this = this, _null = null,
t1 = L.Localizations_of(context, C.Type_AppLocalization_KyD, type$.legacy_AppLocalization),
viewModel = _this._widget.viewModel,
state = viewModel.state,
@ -384923,9 +384925,48 @@
customField2 = t2 ? "task2" : "product2";
customField3 = t2 ? "task3" : "product3";
customField4 = t2 ? "task4" : "product4";
t2 = state.prefState.customColors._map$_map;
t3 = J.getInterceptor$asx(t2);
tableFontColor = t3.$index(t2, "invoice_header_font_color");
if (tableFontColor == null)
tableFontColor = "";
tableHeaderColor = t3.$index(t2, "invoice_header_background_color");
if (tableHeaderColor == null)
tableHeaderColor = "";
if (_this._isReordering) {
t1 = type$.JSArray_legacy_Widget;
return Y.FormCard$(_null, H.setRuntimeTypeInfo([T.Row$(H.setRuntimeTypeInfo([B.IconButton$(C.Alignment_0_0, _null, _null, true, L.Icon$(C.IconData_57706_MaterialIcons_null_false, _null, _null), 24, new E._InvoiceEditItemsDesktopState_build_closure3(_this), C.EdgeInsets_8_8_8_8, _null, _null)], t1), C.CrossAxisAlignment_2, C.MainAxisAlignment_1, C.MainAxisSize_1, _null), new Z.ReorderableListView(new E._InvoiceEditItemsDesktopState_build_closure4(_this, lineItems, company, customField1, customField2, customField3, customField4, hasTax1, hasTax2, hasTax3, invoice, precision), J.get$length$asx(t7), new E._InvoiceEditItemsDesktopState_build_closure5(lineItems, viewModel), false, _null, false, true, _null)], t1), _null, 4, false, _null, false, C.EdgeInsets_12_0_12_0);
t2 = tableHeaderColor.length !== 0 ? new S.BoxDecoration(E.convertHexStringToColor(tableHeaderColor), _null, _null, _null, _null, _null, C.BoxShape_0) : new S.BoxDecoration(_null, _null, _null, _null, _null, _null, C.BoxShape_0);
t3 = type$.JSArray_legacy_Widget;
t4 = H.setRuntimeTypeInfo([T.Expanded$(new E.TableHeader(t1.get$item(t1), false, true, _null), 1), T.Expanded$(new E.TableHeader(t1.get$description(t1), false, false, _null), 2)], t3);
if (company.getCustomFieldLabel$1(customField1).length !== 0)
t4.push(T.Expanded$(new E.TableHeader(company.getCustomFieldLabel$1(customField1), false, false, _null), 1));
if (company.getCustomFieldLabel$1(customField2).length !== 0)
t4.push(T.Expanded$(new E.TableHeader(company.getCustomFieldLabel$1(customField2), false, false, _null), 1));
if (company.getCustomFieldLabel$1(customField3).length !== 0)
t4.push(T.Expanded$(new E.TableHeader(company.getCustomFieldLabel$1(customField3), false, false, _null), 1));
if (company.getCustomFieldLabel$1(customField4).length !== 0)
t4.push(T.Expanded$(new E.TableHeader(company.getCustomFieldLabel$1(customField4), false, false, _null), 1));
if (hasTax1) {
t5 = t1.get$tax();
t4.push(T.Expanded$(new E.TableHeader(t5 + (company.settings.enableInclusiveTaxes ? " - " + t1.get$inclusive() : ""), false, false, _null), 1));
}
if (hasTax2) {
t5 = t1.get$tax();
t4.push(T.Expanded$(new E.TableHeader(t5 + (company.settings.enableInclusiveTaxes ? " - " + t1.get$inclusive() : ""), false, false, _null), 1));
}
if (hasTax3) {
t5 = t1.get$tax();
t4.push(T.Expanded$(new E.TableHeader(t5 + (company.settings.enableInclusiveTaxes ? " - " + t1.get$inclusive() : ""), false, false, _null), 1));
}
t4.push(T.Expanded$(new E.TableHeader(_this._widget.isTasks ? t1.get$rate(t1) : t1.get$unitCost(), true, false, _null), 1));
if (company.enableProductQuantity || _this._widget.isTasks)
t4.push(T.Expanded$(new E.TableHeader(_this._widget.isTasks ? t1.get$hours() : t1.get$quantity(), true, false, _null), 1));
if (company.enableProductDiscount)
t4.push(T.Expanded$(new E.TableHeader(t1.get$discount(), true, false, _null), 1));
t4.push(T.Expanded$(new E.TableHeader(t1.get$lineTotal(), true, false, _null), 1));
t4.push(new T.SizedBox(16, _null, _null, _null));
t1 = tableFontColor.length !== 0 ? E.convertHexStringToColor(tableFontColor) : _null;
t4.push(B.IconButton$(C.Alignment_0_0, t1, _null, true, L.Icon$(C.IconData_57706_MaterialIcons_null_false, _null, _null), 24, new E._InvoiceEditItemsDesktopState_build_closure3(_this), C.EdgeInsets_8_8_8_8, _null, _null));
return Y.FormCard$(_null, H.setRuntimeTypeInfo([M.DecoratedBox$(T.Row$(t4, C.CrossAxisAlignment_2, C.MainAxisAlignment_0, C.MainAxisSize_1, _null), t2, C.DecorationPosition_0), new Z.ReorderableListView(new E._InvoiceEditItemsDesktopState_build_closure4(_this, lineItems, company, customField1, customField2, customField3, customField4, hasTax1, hasTax2, hasTax3, invoice, precision), J.get$length$asx(t7), new E._InvoiceEditItemsDesktopState_build_closure5(lineItems, viewModel), false, _null, false, true, _null)], t3), _null, 4, false, _null, false, C.EdgeInsets_12_0_12_0);
}
t2 = t5.where$1(t7, new E._InvoiceEditItemsDesktopState_build_closure6());
if (!t2.get$iterator(t2).moveNext$0()) {
@ -384952,61 +384993,41 @@
++lastIndex;
if (company.getCustomFieldLabel$1(customField4).length !== 0)
++lastIndex;
t4 = state.prefState.customColors._map$_map;
t5 = J.getInterceptor$asx(t4);
tableFontColor = t5.$index(t4, "invoice_header_font_color");
if (tableFontColor == null)
tableFontColor = "";
tableHeaderColor = t5.$index(t4, "invoice_header_background_color");
if (tableHeaderColor == null)
tableHeaderColor = "";
t4 = t1.localeCode;
t5 = J.$index$asx($.LocalizationsProvider__localizedValues.$index(0, t4), "item");
if (t5 == null)
t5 = "";
t5 = H.setRuntimeTypeInfo([new E.TableHeader(t5, false, true, _null), new E.TableHeader(t1.get$description(t1), false, false, _null)], type$.JSArray_legacy_StatelessWidget);
t4 = H.setRuntimeTypeInfo([new E.TableHeader(t1.get$item(t1), false, true, _null), new E.TableHeader(t1.get$description(t1), false, false, _null)], type$.JSArray_legacy_StatelessWidget);
if (company.getCustomFieldLabel$1(customField1).length !== 0)
t5.push(new E.TableHeader(company.getCustomFieldLabel$1(customField1), false, false, _null));
t4.push(new E.TableHeader(company.getCustomFieldLabel$1(customField1), false, false, _null));
if (company.getCustomFieldLabel$1(customField2).length !== 0)
t5.push(new E.TableHeader(company.getCustomFieldLabel$1(customField2), false, false, _null));
t4.push(new E.TableHeader(company.getCustomFieldLabel$1(customField2), false, false, _null));
if (company.getCustomFieldLabel$1(customField3).length !== 0)
t5.push(new E.TableHeader(company.getCustomFieldLabel$1(customField3), false, false, _null));
t4.push(new E.TableHeader(company.getCustomFieldLabel$1(customField3), false, false, _null));
if (company.getCustomFieldLabel$1(customField4).length !== 0)
t5.push(new E.TableHeader(company.getCustomFieldLabel$1(customField4), false, false, _null));
t4.push(new E.TableHeader(company.getCustomFieldLabel$1(customField4), false, false, _null));
if (hasTax1) {
t7 = t1.get$tax();
t5.push(new E.TableHeader(t7 + (company.settings.enableInclusiveTaxes ? " - " + t1.get$inclusive() : ""), false, false, _null));
t5 = t1.get$tax();
t4.push(new E.TableHeader(t5 + (company.settings.enableInclusiveTaxes ? " - " + t1.get$inclusive() : ""), false, false, _null));
}
if (hasTax2) {
t7 = t1.get$tax();
t5.push(new E.TableHeader(t7 + (company.settings.enableInclusiveTaxes ? " - " + t1.get$inclusive() : ""), false, false, _null));
t5 = t1.get$tax();
t4.push(new E.TableHeader(t5 + (company.settings.enableInclusiveTaxes ? " - " + t1.get$inclusive() : ""), false, false, _null));
}
if (hasTax3) {
t7 = t1.get$tax();
t5.push(new E.TableHeader(t7 + (company.settings.enableInclusiveTaxes ? " - " + t1.get$inclusive() : ""), false, false, _null));
}
t5.push(new E.TableHeader(_this._widget.isTasks ? t1.get$rate(t1) : t1.get$unitCost(), true, false, _null));
if (!t2 || _this._widget.isTasks) {
if (_this._widget.isTasks) {
t7 = J.$index$asx($.LocalizationsProvider__localizedValues.$index(0, t4), "hours");
if (t7 == null)
t7 = "";
} else
t7 = t1.get$quantity();
t5.push(new E.TableHeader(t7, true, false, _null));
t5 = t1.get$tax();
t4.push(new E.TableHeader(t5 + (company.settings.enableInclusiveTaxes ? " - " + t1.get$inclusive() : ""), false, false, _null));
}
t4.push(new E.TableHeader(_this._widget.isTasks ? t1.get$rate(t1) : t1.get$unitCost(), true, false, _null));
if (!t2 || _this._widget.isTasks)
t4.push(new E.TableHeader(_this._widget.isTasks ? t1.get$hours() : t1.get$quantity(), true, false, _null));
if (t3)
t5.push(new E.TableHeader(t1.get$discount(), true, false, _null));
t4 = J.$index$asx($.LocalizationsProvider__localizedValues.$index(0, t4), "line_total");
t5.push(new E.TableHeader(t4 == null ? "" : t4, true, false, _null));
t4 = L.Icon$(_this._isReordering ? C.IconData_57706_MaterialIcons_null_false : C.IconData_58919_MaterialIcons_null_false, _null, _null);
t4.push(new E.TableHeader(t1.get$discount(), true, false, _null));
t4.push(new E.TableHeader(t1.get$lineTotal(), true, false, _null));
t5 = L.Icon$(C.IconData_58919_MaterialIcons_null_false, _null, _null);
t7 = tableFontColor.length !== 0 ? E.convertHexStringToColor(tableFontColor) : _null;
t8 = new H.WhereIterable(includedLineItems, new E._InvoiceEditItemsDesktopState_build_closure7(), H._arrayInstanceType(includedLineItems)._eval$1("WhereIterable<1>"));
t5.push(B.IconButton$(C.Alignment_0_0, t7, _null, true, t4, 24, t8.get$length(t8) < 2 ? _null : new E._InvoiceEditItemsDesktopState_build_closure8(_this), C.EdgeInsets_8_8_8_8, _null, _null));
t4 = P.LinkedHashMap_LinkedHashMap$_literal([0, new S.FlexColumnWidth(1.3), 1, new S.FlexColumnWidth(2.2), lastIndex, new S.FixedColumnWidth(40)], type$.legacy_int, type$.legacy_TableColumnWidth);
t4.push(B.IconButton$(C.Alignment_0_0, t7, _null, true, t5, 24, t8.get$length(t8) < 2 ? _null : new E._InvoiceEditItemsDesktopState_build_closure8(_this), C.EdgeInsets_8_8_8_8, _null, _null));
t5 = P.LinkedHashMap_LinkedHashMap$_literal([0, new S.FlexColumnWidth(1.3), 1, new S.FlexColumnWidth(2.2), lastIndex, new S.FixedColumnWidth(40)], type$.legacy_int, type$.legacy_TableColumnWidth);
t7 = "__datatable_" + H.S(_this._invoice_edit_items_desktop$_updatedAt) + "__";
t9 = type$.ValueKey_legacy_String;
t5 = H.setRuntimeTypeInfo([new S.TableRow(_null, tableHeaderColor.length !== 0 ? new S.BoxDecoration(E.convertHexStringToColor(tableHeaderColor), _null, _null, _null, _null, _null, C.BoxShape_0) : _null, t5)], type$.JSArray_legacy_TableRow);
t4 = H.setRuntimeTypeInfo([new S.TableRow(_null, tableHeaderColor.length !== 0 ? new S.BoxDecoration(E.convertHexStringToColor(tableHeaderColor), _null, _null, _null, _null, _null, C.BoxShape_0) : new S.BoxDecoration(_null, _null, _null, _null, _null, _null, C.BoxShape_0), t4)], type$.JSArray_legacy_TableRow);
for (t8 = type$.PopupMenuButton_legacy_String, t10 = type$.legacy_String, t11 = type$.JSArray_legacy_Widget, index = 0; index < J.get$length$asx(lineItems._copy_on_write_list$_list); ++index) {
if (!(J.$index$asx(lineItems._copy_on_write_list$_list, index).typeId === "2" && _this._widget.isTasks))
t12 = J.$index$asx(lineItems._copy_on_write_list$_list, index).typeId !== "2" && !_this._widget.isTasks || J.get$isEmpty$asx(J.$index$asx(lineItems._copy_on_write_list$_list, index));
@ -385054,10 +385075,10 @@
t13 = "__total_" + index + "_" + H.S(J.total$2$x(J.$index$asx(lineItems._copy_on_write_list$_list, index), invoice, precision)) + "_" + t6 + "__";
t14.push(new T.Padding(C.EdgeInsets_0_0_16_0, E.TextFormField$(true, _null, false, _null, _null, C.InputDecoration_so3, false, false, _null, Y.formatNumber(J.total$2$x(J.$index$asx(lineItems._copy_on_write_list$_list, index), invoice, precision), context, t6, _null, C.FormatNumberType_0, true, _null, false), _null, new D.ValueKey(t13, t9), _null, 1, _null, false, _null, _null, _null, _null, true, _null, C.TextAlign_1, _null, _null), _null));
t14.push(new Z.PopupMenuButton(new E._InvoiceEditItemsDesktopState_build_closure37(includedLineItems, lineItems, index, t1), _null, new E._InvoiceEditItemsDesktopState_build_closure38(_this, t1, viewModel, index, lineItems), _null, C.EdgeInsets_8_8_8_8, _null, new L.Icon(C.IconData_58372_MaterialIcons_null_false, _null, _null, _null), !J.get$isEmpty$asx(J.$index$asx(lineItems._copy_on_write_list$_list, index)), _null, _null, t8));
t5.push(new S.TableRow(new D.ValueKey(t12, t9), _null, t14));
t4.push(new S.TableRow(new D.ValueKey(t12, t9), _null, t14));
}
}
return Y.FormCard$(S.Table$(t5, t4, C.FlexColumnWidth_1, C.TableCellVerticalAlignment_2, new D.ValueKey(t7, t9)), _null, _null, 4, false, _null, false, C.EdgeInsets_12_0_12_0);
return Y.FormCard$(S.Table$(t4, t5, C.FlexColumnWidth_1, C.TableCellVerticalAlignment_2, new D.ValueKey(t7, t9)), _null, _null, 4, false, _null, false, C.EdgeInsets_12_0_12_0);
}
};
E._InvoiceEditItemsDesktopState__updateTable_closure.prototype = {
@ -396923,8 +396944,12 @@
t13.push(new T.SizedBox(28, 28, U.CircularProgressIndicator$(_null, _null, _null, _null, _null, 4, _null, _null), _null));
t13 = T.Row$(t13, C.CrossAxisAlignment_2, C.MainAxisAlignment_0, C.MainAxisSize_0, _null);
t15 = H.setRuntimeTypeInfo([], t12);
if (D.getLayout(context) === C.AppLayout_desktop)
C.JSArray_methods.addAll$1(t15, H.setRuntimeTypeInfo([new T.Builder(new A.ReportsScreen_build_closure13(_this, t1, reportResult), _null), new X.AppTextButton(t1.get$$export(), new A.ReportsScreen_build_closure14(_this, context), true, _null, _null)], t12));
if (D.getLayout(context) === C.AppLayout_desktop) {
t16 = H.setRuntimeTypeInfo([new T.Builder(new A.ReportsScreen_build_closure13(_this, t1, reportResult), _null)], t12);
if (!(D.isMacOS() || D.isWindows() || D.isLinux()))
t16.push(new X.AppTextButton(t1.get$$export(), new A.ReportsScreen_build_closure14(_this, context), true, _null, _null));
C.JSArray_methods.addAll$1(t15, t16);
}
if (D.getLayout(context) === C.AppLayout_mobile || !state.prefState.isHistoryVisible)
t15.push(new T.Builder(new A.ReportsScreen_build_closure15(t1, state, store), _null));
t13 = E.AppBar$(t15, _null, false, _null, _null, _null, 1, _null, false, _null, false, _null, _null, _null, leading, _null, true, _null, _null, _null, _null, t13, _null, _null, _null, 1, _null);
@ -419921,6 +419946,14 @@
var t1 = J.$index$asx($.LocalizationsProvider__localizedValues.$index(0, this.localeCode), "please_enter_a_value");
return t1 == null ? "" : t1;
},
get$item: function(_) {
var t1 = J.$index$asx($.LocalizationsProvider__localizedValues.$index(0, this.localeCode), "item");
return t1 == null ? "" : t1;
},
get$lineTotal: function() {
var t1 = J.$index$asx($.LocalizationsProvider__localizedValues.$index(0, this.localeCode), "line_total");
return t1 == null ? "" : t1;
},
get$contactUs: function() {
var t1 = J.$index$asx($.LocalizationsProvider__localizedValues.$index(0, this.localeCode), "contact_us");
return t1 == null ? "" : t1;
@ -420177,6 +420210,10 @@
var t1 = J.$index$asx($.LocalizationsProvider__localizedValues.$index(0, this.localeCode), "apply_payment");
return t1 == null ? "" : t1;
},
get$hours: function() {
var t1 = J.$index$asx($.LocalizationsProvider__localizedValues.$index(0, this.localeCode), "hours");
return t1 == null ? "" : t1;
},
get$gateway: function() {
var t1 = J.$index$asx($.LocalizationsProvider__localizedValues.$index(0, this.localeCode), "gateway");
return t1 == null ? "" : t1;

View File

@ -1,5 +1,5 @@
{
"/js/app.js": "/js/app.js?id=0e3959ab851d3350364d",
"/js/app.js": "/js/app.js?id=0d1e02ebdcc97462d422",
"/js/clients/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js?id=de4468c682d6861847de",
"/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js?id=cfe5de1cf87a0b01568d",
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=5e74bc0d346beeb57ee9",
@ -11,11 +11,11 @@
"/js/clients/quotes/action-selectors.js": "/js/clients/quotes/action-selectors.js?id=63f0688329be80ee8693",
"/js/clients/quotes/approve.js": "/js/clients/quotes/approve.js?id=795d2f44cf3d117a554e",
"/js/clients/payments/stripe-credit-card.js": "/js/clients/payments/stripe-credit-card.js?id=ea4250be693260798735",
"/js/setup/setup.js": "/js/setup/setup.js?id=7e19431f4cb9ad45e177",
"/js/setup/setup.js": "/js/setup/setup.js?id=6b870beeb350d83668c5",
"/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js?id=8ce33c3deae058ad314f",
"/js/clients/shared/pdf.js": "/js/clients/shared/pdf.js?id=73a0d914ad3577f257f4",
"/js/clients/shared/multiple-downloads.js": "/js/clients/shared/multiple-downloads.js?id=c2caa29f753ad1f3a12c",
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=44c51b4838d1f135bbe3",
"/js/clients/linkify-urls.js": "/js/clients/linkify-urls.js?id=448d055fa1e8357130e6",
"/js/clients/payments/braintree-credit-card.js": "/js/clients/payments/braintree-credit-card.js?id=a334dd9257dd510a1feb",
"/js/clients/payments/braintree-paypal.js": "/js/clients/payments/braintree-paypal.js?id=37950e8a39281d2f596a",
"/js/clients/payments/wepay-credit-card.js": "/js/clients/payments/wepay-credit-card.js?id=ba4d5b7175117ababdb2",
@ -26,7 +26,7 @@
"/js/clients/payment_methods/braintree-ach.js": "/js/clients/payment_methods/braintree-ach.js?id=656ad159838b726969f5",
"/js/clients/payments/square-credit-card.js": "/js/clients/payments/square-credit-card.js?id=8f05ce6bd2d6cae7e5f2",
"/js/clients/statements/view.js": "/js/clients/statements/view.js?id=ea3db0c04b4372f76735",
"/js/clients/payments/razorpay-aio.js": "/js/clients/payments/razorpay-aio.js?id=1dae47a0b783814ce895",
"/js/clients/payments/razorpay-aio.js": "/js/clients/payments/razorpay-aio.js?id=c36ab5621413ef1de7c8",
"/js/clients/payments/stripe-sepa.js": "/js/clients/payments/stripe-sepa.js?id=cbd7bb4c483ca75333f4",
"/js/clients/payment_methods/authorize-checkout-card.js": "/js/clients/payment_methods/authorize-checkout-card.js?id=61becda97682c7909f29",
"/js/clients/payments/stripe-giropay.js": "/js/clients/payments/stripe-giropay.js?id=cdf300d72a1564d19b72",

View File

@ -12,7 +12,7 @@ let options = JSON.parse(
document.querySelector('meta[name=razorpay-options]')?.content
);
options.handler = function(response) {
options.handler = function (response) {
document.getElementById('razorpay_payment_id').value =
response.razorpay_payment_id;
document.getElementById('razorpay_signature').value =
@ -20,10 +20,17 @@ options.handler = function(response) {
document.getElementById('server-response').submit();
};
let razorpay = new Razorpay(options);
options.modal = {
ondismiss: function () {
payNowButton.disabled = false;
},
};
document.getElementById('pay-now').onclick = function(event) {
event.target.parentElement.disabled = true;
let razorpay = new Razorpay(options);
let payNowButton = document.getElementById('pay-now');
payNowButton.onclick = function (event) {
payNowButton.disabled = true;
razorpay.open();
};

View File

@ -4339,6 +4339,7 @@ $LANG = array(
'browser_pay' => 'Google Pay, Apple Pay, Microsoft Pay',
'no_available_methods' => 'We can\'t find any credit cards on your device. <a href="https://invoiceninja.github.io/docs/payments#apple-pay-google-pay-microsoft-pay" target="_blank" class="underline">Read more about this.</a>',
'gocardless_mandate_not_ready' => 'Payment mandate is not ready. Please try again later.',
'payment_type_instant_bank_pay' => 'Instant Bank Pay',
);
return $LANG;

View File

@ -25,8 +25,8 @@
@foreach($multiple_contacts as $contact)
<a data-turbolinks="false"
href="{{ route('client.switch_company', $contact->hashed_id) }}"
class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ $contact->company->present()->name() }}
- {{ $contact->client->present()->name()}}</a>
class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ $contact->client->present()->name()}} - {{ $contact->company->present()->name() }}
</a>
@endforeach
</div>
</div>

View File

@ -23,7 +23,7 @@ Route::get('tmp_pdf/{hash}', 'ClientPortal\TempRouteController@index')->name('tm
Route::get('client/key_login/{contact_key}', 'ClientPortal\ContactHashLoginController@login')->name('client.contact_login')->middleware(['domain_db','contact_key_login']);
Route::get('client/magic_link/{magic_link}', 'ClientPortal\ContactHashLoginController@magicLink')->name('client.contact_magic_link')->middleware(['domain_db','contact_key_login']);
Route::get('documents/{document_hash}', 'ClientPortal\DocumentController@publicDownload')->name('documents.public_download')->middleware(['document_db']);
Route::get('documents/{document_hash}', 'ClientPortal\DocumentController@publicDownload')->name('documents.public_download')->middleware(['domain_db']);
Route::get('error', 'ClientPortal\ContactHashLoginController@errorPage')->name('client.error');
Route::get('client/payment/{contact_key}/{payment_id}', 'ClientPortal\InvitationController@paymentRouter')->middleware(['domain_db','contact_key_login']);
Route::get('client/ninja/{contact_key}/{company_key}', 'ClientPortal\NinjaPlanController@index')->name('client.ninja_contact_login')->middleware(['domain_db']);

View File

@ -43,3 +43,4 @@ Route::get('stripe/completed', 'StripeConnectController@completed')->name('strip
Route::get('checkout/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', 'Gateways\Checkout3dsController@index')->name('checkout.3ds_redirect');
Route::get('mollie/3ds_redirect/{company_key}/{company_gateway_id}/{hash}', 'Gateways\Mollie3dsController@index')->name('mollie.3ds_redirect');
Route::get('gocardless/ibp_redirect/{company_key}/{company_gateway_id}/{hash}', 'Gateways\GoCardlessController@ibpRedirect')->name('gocardless.ibp_redirect');

View File

@ -10,6 +10,7 @@
*/
namespace Tests\Unit;
use App\Factory\ClientContactFactory;
use App\Factory\InvoiceItemFactory;
use App\Utils\Traits\UserSessionAttributes;
use Illuminate\Support\Facades\Session;
@ -77,4 +78,41 @@ class CollectionMergingTest extends TestCase
$this->assertTrue(collect($items)->contains('type_id', 3));
}
public function testClientContactSendEmailExists()
{
$new_collection = collect();
$cc = ClientContactFactory::create(1,1);
$cc->send_email = true;
$new_collection->push($cc);
$cc_false = ClientContactFactory::create(1,1);
$cc_false->send_email = false;
$new_collection->push($cc_false);
$this->assertTrue($new_collection->contains('send_email', true));
}
public function testClientContactSendEmailDoesNotExists()
{
$new_collection = collect();
$cc = ClientContactFactory::create(1,1);
$cc->send_email = false;
$new_collection->push($cc);
$cc_false = ClientContactFactory::create(1,1);
$cc_false->send_email = false;
$new_collection->push($cc_false);
$this->assertFalse($new_collection->contains('send_email', true));
}
}