Merge pull request #4682 from turbo124/v5-stable

5.0.47
This commit is contained in:
David Bomba 2021-01-14 11:14:57 +11:00 committed by GitHub
commit 751c675ac1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
104 changed files with 78376 additions and 77601 deletions

View File

@ -1 +1 @@
5.0.46
5.0.47

View File

@ -62,7 +62,7 @@ class SendRemindersCron extends Command
$invoices = Invoice::where('is_deleted', 0)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->where('balance', '>', 0)
->whereDate('due_date', now()->subDays(1)->startOfDay())
->whereDate('due_date', '<=', now()->subDays(1)->startOfDay())
->cursor();
$invoices->each(function ($invoice) {
@ -74,7 +74,7 @@ class SendRemindersCron extends Command
{
$quotes = Quote::where('is_deleted', 0)
->where('status_id', Quote::STATUS_SENT)
->whereDate('due_date', now()->subDays(1)->startOfDay())
->whereDate('due_date', '<=', now()->subDays(1)->startOfDay())
->cursor();
$quotes->each(function ($quote) {

View File

@ -30,7 +30,7 @@ class CompanySettings extends BaseSettings
public $enable_client_portal_tasks = false; //@ben to implement
public $enable_client_portal_password = false; //@implemented
public $enable_client_portal = true; //@implemented
public $enable_client_portal_dashboard = true; // @TODO There currently is no dashboard so this is pending
public $enable_client_portal_dashboard = false; // @TODO There currently is no dashboard so this is pending
public $signature_on_pdf = false; //@implemented
public $document_email_attachment = false; //@TODO I assume this is 3rd party attachments on the entity to be included
@ -634,6 +634,7 @@ class CompanySettings extends BaseSettings
'$product.description',
'$product.unit_cost',
'$product.quantity',
'$product.discount',
'$product.tax',
'$product.line_total',
],
@ -642,6 +643,7 @@ class CompanySettings extends BaseSettings
'$task.description',
'$task.rate',
'$task.hours',
'$task.discount',
'$task.tax',
'$task.line_total',
],

View File

@ -223,6 +223,9 @@ class EmailTemplateDefaults
private static function transformText($string)
{
//preformat the string, removing trailing colons.
$string = rtrim($string, ":");
return str_replace(':', '$', ctrans('texts.'.$string));
}
}

View File

@ -27,6 +27,7 @@ class CreditWasEmailed
public $event_vars;
public $template;
/**
* Create a new event instance.
*
@ -34,10 +35,11 @@ class CreditWasEmailed
* @param Company $company
* @param array $event_vars
*/
public function __construct(CreditInvitation $invitation, Company $company, array $event_vars)
public function __construct(CreditInvitation $invitation, Company $company, array $event_vars, string $template)
{
$this->invitation = $invitation;
$this->company = $company;
$this->event_vars = $event_vars;
$this->template = $template;
}
}

View File

@ -31,6 +31,8 @@ class InvoiceWasEmailed
public $event_vars;
public $template;
/**
* Create a new event instance.
*
@ -38,10 +40,11 @@ class InvoiceWasEmailed
* @param Company $company
* @param array $event_vars
*/
public function __construct(InvoiceInvitation $invitation, Company $company, array $event_vars)
public function __construct(InvoiceInvitation $invitation, Company $company, array $event_vars, string $template)
{
$this->invitation = $invitation;
$this->company = $company;
$this->event_vars = $event_vars;
$this->template = $template;
}
}

View File

@ -29,6 +29,7 @@ class QuoteWasEmailed
public $event_vars;
public $template;
/**
* Create a new event instance.
*
@ -37,10 +38,11 @@ class QuoteWasEmailed
* @param Company $company
* @param array $event_vars
*/
public function __construct(QuoteInvitation $invitation, Company $company, array $event_vars)
public function __construct(QuoteInvitation $invitation, Company $company, array $event_vars, string $template)
{
$this->invitation = $invitation;
$this->company = $company;
$this->event_vars = $event_vars;
$this->template = $template;
}
}

View File

@ -37,7 +37,7 @@ class CloneQuoteToInvoiceFactory
$invoice->due_date = null;
$invoice->partial_due_date = null;
$invoice->number = null;
$invoice->date = now()->format('Y-m-d');
return $invoice;
}
}

View File

@ -88,9 +88,11 @@ class InvoiceItemSum
return $this;
}
/* Don't round the cost x qty - will allow us to use higher precision costs */
private function sumLineItem()
{ //todo need to support quantities less than the precision amount
$this->setLineTotal($this->formatValue($this->item->cost, $this->currency->precision) * $this->formatValue($this->item->quantity, $this->currency->precision));
// $this->setLineTotal($this->formatValue($this->item->cost, $this->currency->precision) * $this->formatValue($this->item->quantity, $this->currency->precision));
$this->setLineTotal($this->item->cost * $this->item->quantity);
return $this;
}
@ -112,8 +114,8 @@ class InvoiceItemSum
{
$item_tax = 0;
// info(print_r($this->item,1));
// info(print_r($this->invoice,1));
// nlog(print_r($this->item,1));
// nlog(print_r($this->invoice,1));
$amount = $this->item->line_total - ($this->item->line_total * ($this->invoice->discount / 100));
$item_tax_rate1_total = $this->calcAmountLineTax($this->item->tax_rate1, $amount);

View File

@ -22,6 +22,6 @@ class DashboardController extends Controller
*/
public function index()
{
return redirect()->route('client.invoices.index');
return $this->render('dashboard.index');
}
}

View File

@ -51,6 +51,7 @@ class InvitationController extends Controller
->with('contact.client')
->firstOrFail();
/* Return early if we have the correct client_hash embedded */
if (request()->has('client_hash') && request()->input('client_hash') == $invitation->contact->client->client_hash) {

View File

@ -32,6 +32,7 @@ use App\Transformers\CompanyTransformer;
use App\Transformers\CompanyUserTransformer;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\SavesDocuments;
use App\Utils\Traits\Uploadable;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Http\Request;
@ -47,6 +48,7 @@ class CompanyController extends BaseController
use DispatchesJobs;
use MakesHash;
use Uploadable;
use SavesDocuments;
protected $entity_type = Company::class;
@ -402,14 +404,17 @@ class CompanyController extends BaseController
*/
public function update(UpdateCompanyRequest $request, Company $company)
{
if ($request->hasFile('company_logo') || (is_array($request->input('settings')) && !array_key_exists('company_logo', $request->input('settings')))) {
if ($request->hasFile('company_logo') || (is_array($request->input('settings')) && !array_key_exists('company_logo', $request->input('settings'))))
$this->removeLogo($company);
}
$company = $this->company_repo->save($request->all(), $company);
$company->saveSettings($request->input('settings'), $company);
if ($request->has('documents'))
$this->saveDocuments($request->input('documents'), $company, false);
$this->uploadLogo($request->file('company_logo'), $company, $company);
return $this->itemResponse($company);

View File

@ -1,38 +0,0 @@
<?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://opensource.org/licenses/AAL
*/
namespace App\Http\Controllers;
use Illuminate\Http\Response;
class DashboardController extends BaseController
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth:user');
}
/**
* Show the application dashboard.
*
* @return Response
*/
public function index()
{
// dd(json_decode(auth()->user()->permissions(),true));
return view('dashboard.index');
}
}

View File

@ -140,15 +140,13 @@ class EmailController extends BaseController
$entity_obj->save();
/*Only notify the admin ONCE, not once per contact/invite*/
// $invitation = $entity_obj->invitations->first();
// EntitySentMailer::dispatch($invitation, $entity_string, $entity_obj->user, $invitation->company);
if ($entity_obj instanceof Invoice) {
$this->entity_type = Invoice::class;
$this->entity_transformer = InvoiceTransformer::class;
if ($entity_obj->invitations->count() >= 1) {
$entity_obj->entityEmailEvent($entity_obj->invitations->first(), 'invoice');
$entity_obj->entityEmailEvent($entity_obj->invitations->first(), 'invoice', $template);
}
}
@ -157,7 +155,7 @@ class EmailController extends BaseController
$this->entity_transformer = QuoteTransformer::class;
if ($entity_obj->invitations->count() >= 1) {
event(new QuoteWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars()));
event(new QuoteWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars(), 'quote'));
}
}
@ -166,7 +164,7 @@ class EmailController extends BaseController
$this->entity_transformer = CreditTransformer::class;
if ($entity_obj->invitations->count() >= 1) {
event(new CreditWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars()));
event(new CreditWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars(), 'credit'));
}
}

View File

@ -426,10 +426,9 @@ class ExpenseController extends BaseController
*/
public function destroy(DestroyExpenseRequest $request, Expense $expense)
{
//may not need these destroy routes as we are using actions to 'archive/delete'
$expense->delete();
$this->expense_repo->delete($expense);
return response()->json([], 200);
return $this->itemResponse($expense->fresh());
}
/**

View File

@ -0,0 +1,148 @@
<?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://opensource.org/licenses/AAL
*/
namespace App\Http\Livewire;
use App\Models\ClientContact;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Livewire\Component;
class RequiredClientInfo extends Component
{
public $fields = [];
/**
* @var ClientContact
*/
public $contact;
/**
* Mappings for updating the database. Left side is mapping from gateway,
* right side is column in database.
*
* @var string[]
*/
private $mappings = [
'client_name' => 'name',
'client_website' => 'website',
'client_phone' => 'phone',
'client_address_line_1' => 'address1',
'client_address_line_2' => 'address2',
'client_city' => 'city',
'client_state' => 'state',
'client_postal_code' => 'postal_code',
'client_country_id' => 'country_id',
'client_shipping_address_line_1' => 'shipping_address1',
'client_shipping_address_line_2' => 'shipping_address2',
'client_shipping_city' => 'shipping_city',
'client_shipping_state' => 'shipping_state',
'client_shipping_postal_code' => 'shipping_postal_code',
'client_shipping_country_id' => 'shipping_country_id',
'contact_first_name' => 'first_name',
'contact_last_name' => 'last_name',
'contact_email' => 'email',
'contact_phone' => 'phone',
];
public $show_form = true;
public function handleSubmit(array $data): bool
{
$rules = [];
collect($this->fields)->map(function ($field) use (&$rules) {
$rules[$field['name']] = array_key_exists('validation_rules', $field)
? $field['validation_rules']
: 'required';
});
$validator = Validator::make($data, $rules);
if ($validator->fails()) {
session()->flash('validation_errors', $validator->getMessageBag()->getMessages());
return false;
}
if ($this->updateClientDetails($data)) {
$this->emit('passed-required-fields-check');
return true;
}
// TODO: Throw an exception about not being able to update the profile.
return false;
}
private function updateClientDetails(array $data): bool
{
$client = [];
$contact = [];
foreach ($data as $field => $value) {
if (Str::startsWith($field, 'client_')) {
$client[$this->mappings[$field]] = $value;
}
if (Str::startsWith($field, 'contact_')) {
$contact[$this->mappings[$field]] = $value;
}
}
$contact_update = $this->contact
->fill($contact)
->push();
$client_update = $this->contact->client
->fill($client)
->push();
if ($contact_update && $client_update) {
return true;
}
return false;
}
public function checkFields()
{
foreach ($this->fields as $field) {
$_field = $this->mappings[$field['name']];
if (Str::startsWith($field['name'], 'client_')) {
(empty($this->contact->client->{$_field}) || is_null($this->contact->client->{$_field}))
? $this->show_form = true
: $this->show_form = false;
}
if (Str::startsWith($field['name'], 'contact_')) {
(empty($this->contact->{$_field}) || is_null($this->contact->{$_field}))
? $this->show_form = true
: $this->show_form = false;
}
}
}
public function render()
{
count($this->fields) > 0
? $this->checkFields()
: $this->show_form = false;
return render('components.livewire.required-client-info');
}
}

View File

@ -50,7 +50,7 @@ class StoreClientRequest extends Request
//$rules['name'] = 'required|min:1';
$rules['id_number'] = 'unique:clients,id_number,'.$this->id.',id,company_id,'.$this->company_id;
$rules['settings'] = new ValidClientGroupSettingsRule();
$rules['contacts.*.email'] = 'nullable|distinct';
$rules['contacts.*.email'] = 'bail|nullable|distinct|sometimes|email';
$rules['contacts.*.password'] = [
'nullable',
'sometimes',
@ -85,10 +85,12 @@ class StoreClientRequest extends Request
$input = $this->decodePrimaryKeys($input);
if(isset($input['group_settings_id']))
$input['group_settings_id'] = $this->decodePrimaryKey($input['group_settings_id']);
//is no settings->currency_id is set then lets dive in and find either a group or company currency all the below may be redundant!!
if (! property_exists($settings, 'currency_id') && isset($input['group_settings_id'])) {
$input['group_settings_id'] = $this->decodePrimaryKey($input['group_settings_id']);
$group_settings = GroupSetting::find($input['group_settings_id']);
if ($group_settings && property_exists($group_settings->settings, 'currency_id') && isset($group_settings->settings->currency_id)) {

View File

@ -54,7 +54,7 @@ class UpdateClientRequest extends Request
//$rules['id_number'] = 'unique:clients,id_number,,id,company_id,' . auth()->user()->company()->id;
$rules['id_number'] = 'unique:clients,id_number,'.$this->id.',id,company_id,'.$this->company_id;
$rules['settings'] = new ValidClientGroupSettingsRule();
$rules['contacts.*.email'] = 'nullable|distinct';
$rules['contacts.*.email'] = 'bail|nullable|distinct|sometimes|email';
$rules['contacts.*.password'] = [
'nullable',
'sometimes',

View File

@ -54,6 +54,7 @@ class StoreCreditRequest extends Request
$rules['client_id'] = 'required|exists:clients,id,company_id,'.auth()->user()->company()->id;
$rules['number'] = new UniqueCreditNumberRule($this->all());
$rules['line_items'] = 'array';
return $rules;
}

View File

@ -56,6 +56,8 @@ class UpdateCreditRequest extends Request
$rules['number'] = 'unique:credits,number,'.$this->id.',id,company_id,'.$this->credit->company_id;
}
$rules['line_items'] = 'array';
return $rules;
}

View File

@ -55,6 +55,8 @@ class StoreInvoiceRequest extends Request
$rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())];
$rules['line_items'] = 'array';
return $rules;
}

View File

@ -54,6 +54,8 @@ class UpdateInvoiceRequest extends Request
$rules['number'] = 'unique:invoices,number,'.$this->id.',id,company_id,'.$this->invoice->company_id;
}
$rules['line_items'] = 'array';
return $rules;
}

View File

@ -80,7 +80,7 @@ class StorePaymentRequest extends Request
$input['amount'] = $invoices_total - $credits_total;
}
$input['is_manual'] = true;
// $input['is_manual'] = true;
if (! isset($input['date'])) {
$input['date'] = now()->format('Y-m-d');

View File

@ -94,6 +94,7 @@ class StoreQuoteRequest extends Request
}
$rules['number'] = new UniqueQuoteNumberRule($this->all());
$rules['line_items'] = 'array';
return $rules;
}

View File

@ -50,6 +50,8 @@ class UpdateQuoteRequest extends Request
$rules['number'] = 'unique:quotes,number,'.$this->id.',id,company_id,'.$this->quote->company_id;
}
$rules['line_items'] = 'array';
return $rules;
}

View File

@ -39,9 +39,9 @@ class StoreUserRequest extends Request
$rules['last_name'] = 'required|string|max:100';
if (config('ninja.db.multi_db_enabled')) {
$rules['email'] = [new ValidUserForCompany(), Rule::unique('users')];
$rules['email'] = ['email', new ValidUserForCompany(), Rule::unique('users')];
} else {
$rules['email'] = Rule::unique('users');
$rules['email'] = ['email',Rule::unique('users')];
}

View File

@ -35,7 +35,7 @@ class UpdateUserRequest extends Request
];
if (isset($input['email'])) {
$rules['email'] = ['email:rfc,dns', 'sometimes', new UniqueUserRule($this->user, $input['email'])];
$rules['email'] = ['email', 'sometimes', new UniqueUserRule($this->user, $input['email'])];
}
return $rules;

View File

@ -21,6 +21,8 @@ use Illuminate\View\View;
*/
class PortalComposer
{
public $settings;
/**
* Bind data to the view.
*
@ -45,13 +47,15 @@ class PortalComposer
return [];
}
$this->settings = auth()->user()->client->getMergedSettings();
$data['sidebar'] = $this->sidebarMenu();
$data['header'] = [];
$data['footer'] = [];
$data['countries'] = TranslationHelper::getCountries();
$data['company'] = auth()->user()->company;
$data['client'] = auth()->user()->client;
$data['settings'] = auth()->user()->client->getMergedSettings();
$data['settings'] = $this->settings;
$data['currencies'] = TranslationHelper::getCurrencies();
$data['multiple_contacts'] = session()->get('multiple_contacts');
@ -63,7 +67,9 @@ class PortalComposer
{
$data = [];
// $data[] = [ 'title' => ctrans('texts.dashboard'), 'url' => 'client.dashboard', 'icon' => 'activity'];
if($this->settings->enable_client_portal_dashboard == TRUE)
$data[] = [ 'title' => ctrans('texts.dashboard'), 'url' => 'client.dashboard', 'icon' => 'activity'];
$data[] = ['title' => ctrans('texts.invoices'), 'url' => 'client.invoices.index', 'icon' => 'file-text'];
$data[] = ['title' => ctrans('texts.recurring_invoices'), 'url' => 'client.recurring_invoices.index', 'icon' => 'file'];
$data[] = ['title' => ctrans('texts.payments'), 'url' => 'client.payments.index', 'icon' => 'credit-card'];

View File

@ -39,6 +39,7 @@ class EntitySentMailer extends BaseMailerJob implements ShouldQueue
public $settings;
public $template;
/**
* Create a new job instance.
*
@ -47,7 +48,7 @@ class EntitySentMailer extends BaseMailerJob implements ShouldQueue
* @param $user
* @param $company
*/
public function __construct($invitation, $entity_type, $user, $company)
public function __construct($invitation, $entity_type, $user, $company, $template)
{
$this->company = $company;
@ -60,6 +61,8 @@ class EntitySentMailer extends BaseMailerJob implements ShouldQueue
$this->entity_type = $entity_type;
$this->settings = $invitation->contact->client->getMergedSettings();
$this->template = $template;
}
/**
@ -69,6 +72,8 @@ class EntitySentMailer extends BaseMailerJob implements ShouldQueue
*/
public function handle()
{
nlog("entity sent mailer");
/*If we are migrating data we don't want to fire these notification*/
if ($this->company->is_disabled) {
return true;
@ -80,7 +85,7 @@ class EntitySentMailer extends BaseMailerJob implements ShouldQueue
//if we need to set an email driver do it now
$this->setMailDriver();
$mail_obj = (new EntitySentObject($this->invitation, $this->entity_type))->build();
$mail_obj = (new EntitySentObject($this->invitation, $this->entity_type, $this->template))->build();
$mail_obj->from = [config('mail.from.address'), config('mail.from.name')];
try {

View File

@ -216,7 +216,7 @@ class SendReminders implements ShouldQueue
if ($this->checkSendSetting($invoice, $template)) {
event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars()));
event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars(), $template));
}
$invoice->last_sent_date = now();

View File

@ -94,10 +94,7 @@ class SendRecurring implements ShouldQueue
nlog("last send date = " . $this->recurring_invoice->last_sent_date);
$this->recurring_invoice->save();
//this is duplicated!!
// if ($invoice->invitations->count() > 0)
// event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars()));
}
public function failed($exception = null)

View File

@ -66,7 +66,7 @@ class ReminderJob implements ShouldQueue
});
if ($invoice->invitations->count() > 0) {
event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars()));
event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars(), $reminder_template));
}
} else {
$invoice->next_send_date = null;

View File

@ -33,7 +33,7 @@ class SystemLogger implements ShouldQueue
protected $client;
public function __construct($log, $category_id, $event_id, $type_id, Client $client)
public function __construct($log, $category_id, $event_id, $type_id, ?Client $client)
{
$this->log = $log;
$this->category_id = $category_id;

View File

@ -66,6 +66,9 @@ class UploadFile implements ShouldQueue
*/
public function handle() : ?Document
{
if(is_array($this->file)) //return early if the payload is just JSON
return null;
$path = self::PROPERTIES[$this->type]['path'];
if ($this->company) {

View File

@ -10,7 +10,9 @@
*/
namespace App\Jobs\Util;
use App\Jobs\Util\SystemLogger;
use App\Libraries\MultiDB;
use App\Models\SystemLog;
use App\Models\Webhook;
use App\Transformers\ArraySerializer;
use GuzzleHttp\Client;
@ -113,6 +115,15 @@ class WebhookHandler implements ShouldQueue
if ($response->getStatusCode() == 410 || $response->getStatusCode() == 200) {
$subscription->delete();
}
SystemLogger::dispatch(
$e->getMessage(),
SystemLog::CATEGORY_WEBHOOK,
SystemLog::EVENT_WEBHOOK_RESPONSE,
SystemLog::TYPE_WEBHOOK_RESPONSE,
$this->company->clients->first(),
);
}
public function failed($exception)

View File

@ -51,7 +51,7 @@ class CreditEmailedNotification implements ShouldQueue
if (($key = array_search('mail', $methods)) !== false && $first_notification_sent === true) {
unset($methods[$key]);
EntitySentMailer::dispatch($event->invitation, 'credit', $user, $event->invitation->company);
EntitySentMailer::dispatch($event->invitation, 'credit', $user, $event->invitation->company, $event->template);
$first_notification_sent = false;
}

View File

@ -36,8 +36,26 @@ class CreateInvoicePdf implements ShouldQueue
{
MultiDB::setDb($event->company->db);
$event->invoice->invitations->each(function ($invitation) {
CreateEntityPdf::dispatch($invitation);
});
if(isset($event->invoice))
{
$event->invoice->invitations->each(function ($invitation) {
CreateEntityPdf::dispatch($invitation);
});
}
if(isset($event->quote))
{
$event->quote->invitations->each(function ($invitation) {
CreateEntityPdf::dispatch($invitation);
});
}
if(isset($event->credit))
{
$event->credit->invitations->each(function ($invitation) {
CreateEntityPdf::dispatch($invitation);
});
}
}
}

View File

@ -51,7 +51,7 @@ class InvoiceEmailedNotification implements ShouldQueue
if (($key = array_search('mail', $methods)) !== false && $first_notification_sent === true) {
unset($methods[$key]);
EntitySentMailer::dispatch($event->invitation, 'invoice', $user, $event->invitation->company);
EntitySentMailer::dispatch($event->invitation, 'invoice', $user, $event->invitation->company, $event->template);
$first_notification_sent = false;
}

View File

@ -51,7 +51,7 @@ class QuoteEmailedNotification implements ShouldQueue
if (($key = array_search('mail', $methods)) !== false && $first_notification_sent === true) {
unset($methods[$key]);
EntitySentMailer::dispatch($event->invitation, 'quote', $user, $event->invitation->company);
EntitySentMailer::dispatch($event->invitation, 'quote', $user, $event->invitation->company, $event->template);
$first_notification_sent = false;
}
@ -60,4 +60,4 @@ class QuoteEmailedNotification implements ShouldQueue
$user->notify($notification);
}
}
}
}

View File

@ -28,17 +28,26 @@ class EntitySentObject
public $settings;
public function __construct($invitation, $entity_type)
public $template;
private $template_subject;
private $template_body;
public function __construct($invitation, $entity_type, $template)
{
$this->invitation = $invitation;
$this->entity_type = $entity_type;
$this->entity = $invitation->{$entity_type};
$this->contact = $invitation->contact;
$this->company = $invitation->company;
$this->template = $template;
}
public function build()
{
$this->setTemplate();
$mail_obj = new stdClass;
$mail_obj->amount = $this->getAmount();
$mail_obj->subject = $this->getSubject();
@ -49,6 +58,47 @@ class EntitySentObject
return $mail_obj;
}
private function setTemplate()
{
nlog($this->template);
switch ($this->template) {
case 'invoice':
$this->template_subject = "texts.notification_invoice_sent_subject";
$this->template_body = "texts.notification_invoice_sent";
break;
case 'reminder1':
$this->template_subject = "texts.notification_invoice_reminder1_sent_subject";
$this->template_body = "texts.notification_invoice_sent";
break;
case 'reminder2':
$this->template_subject = "texts.notification_invoice_reminder2_sent_subject";
$this->template_body = "texts.notification_invoice_sent";
break;
case 'reminder3':
$this->template_subject = "texts.notification_invoice_reminder3_sent_subject";
$this->template_body = "texts.notification_invoice_sent";
break;
case 'reminder_endless':
$this->template_subject = "texts.notification_invoice_reminder_endless_sent_subject";
$this->template_body = "texts.notification_invoice_sent";
break;
case 'quote':
$this->template_subject = "texts.notification_quote_sent_subject";
$this->template_body = "texts.notification_quote_sent";
break;
case 'credit':
$this->template_subject = "texts.notification_credit_sent_subject";
$this->template_body = "texts.notification_credit_sent";
break;
default:
$this->template_subject = "texts.notification_invoice_sent_subject";
$this->template_body = "texts.notification_invoice_sent";
break;
}
}
private function getAmount()
{
return Number::formatMoney($this->entity->amount, $this->entity->client);
@ -58,7 +108,7 @@ class EntitySentObject
{
return
ctrans(
"texts.notification_{$this->entity_type}_sent_subject",
$this->template_subject,
[
'client' => $this->contact->present()->name(),
'invoice' => $this->entity->number,
@ -73,7 +123,7 @@ class EntitySentObject
return [
'title' => $this->getSubject(),
'message' => ctrans(
"texts.notification_{$this->entity_type}_sent",
$this->template_body,
[
'amount' => $this->getAmount(),
'client' => $this->contact->present()->name(),

View File

@ -52,7 +52,6 @@ class Credit extends BaseModel
'terms',
'public_notes',
'private_notes',
'invoice_type_id',
'tax_name1',
'tax_rate1',
'tax_name2',

View File

@ -63,7 +63,6 @@ class Invoice extends BaseModel
'terms',
'public_notes',
'private_notes',
'invoice_type_id',
'tax_name1',
'tax_rate1',
'tax_name2',
@ -434,12 +433,11 @@ class Invoice extends BaseModel
return $this->calc()->getTotal();
}
public function entityEmailEvent($invitation, $reminder_template)
public function entityEmailEvent($invitation, $reminder_template, $template)
{
switch ($reminder_template) {
case 'invoice':
event(new InvoiceWasEmailed($invitation, $invitation->company, Ninja::eventVars()));
event(new InvoiceWasEmailed($invitation, $invitation->company, Ninja::eventVars(), $template));
break;
case 'reminder1':
event(new InvoiceReminderWasEmailed($invitation, $invitation->company, Ninja::eventVars(), Activity::INVOICE_REMINDER1_SENT));

View File

@ -59,7 +59,7 @@ class Payment extends BaseModel
'date',
'transaction_reference',
'number',
'is_manual',
// 'is_manual',
'private_notes',
'custom_value1',
'custom_value2',

View File

@ -36,7 +36,7 @@ class CompanyPresenter extends EntityPresenter
$settings = $this->entity->settings;
}
return (strlen($settings->company_logo) > 0) ? url($settings->company_logo) : 'https://www.invoiceninja.com/wp-content/uploads/2019/01/InvoiceNinja-Logo-Round-300x300.png';
return (strlen($settings->company_logo) > 0) ? url('') . $settings->company_logo : 'https://www.invoiceninja.com/wp-content/uploads/2019/01/InvoiceNinja-Logo-Round-300x300.png';
}
public function address($settings = null)

View File

@ -52,7 +52,6 @@ class Quote extends BaseModel
'public_notes',
'private_notes',
'project_id',
'invoice_type_id',
'tax_name1',
'tax_rate1',
'tax_name2',

View File

@ -33,6 +33,8 @@ class SystemLog extends Model
/* Category IDs */
const CATEGORY_GATEWAY_RESPONSE = 1;
const CATEGORY_MAIL = 2;
const CATEGORY_WEBHOOK = 3;
const CATEGORY_PDF = 3;
/* Event IDs*/
const EVENT_PAYMENT_RECONCILIATION_FAILURE = 10;
@ -45,6 +47,9 @@ class SystemLog extends Model
const EVENT_MAIL_SEND = 30;
const EVENT_MAIL_RETRY_QUEUE = 31; //we use this to queue emails that are spooled and not sent due to the email queue quota being exceeded.
const EVENT_WEBHOOK_RESPONSE = 40;
const EVENT_PDF_RESPONSE = 50;
/*Type IDs*/
const TYPE_PAYPAL = 300;
const TYPE_STRIPE = 301;
@ -56,6 +61,10 @@ class SystemLog extends Model
const TYPE_QUOTA_EXCEEDED = 400;
const TYPE_UPSTREAM_FAILURE = 401;
const TYPE_WEBHOOK_RESPONSE = 500;
const TYPE_PDF_FAILURE = 600;
const TYPE_PDF_SUCCESS = 601;
protected $fillable = [
'client_id',
'company_id',

View File

@ -75,7 +75,7 @@ class AuthorizeCreditCard
$client_gateway_token = $authorise_payment_method->createClientGatewayToken($payment_profile, $gateway_customer_reference);
}
$data = (new ChargePaymentProfile($this->authorize))->chargeCustomerProfile($gateway_customer_reference, $payment_profile_id, $data['total']['amount_with_fee']);
$data = (new ChargePaymentProfile($this->authorize))->chargeCustomerProfile($gateway_customer_reference, $payment_profile_id, $data['amount_with_fee']);
return $this->handleResponse($data, $request);
}
@ -201,10 +201,10 @@ class AuthorizeCreditCard
];
SystemLogger::dispatch(
$logger_message,
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_AUTHORIZE,
$logger_message,
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_AUTHORIZE,
$this->authorize->client);
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);

View File

@ -63,6 +63,14 @@ class AuthorizePaymentDriver extends BaseDriver
return $types;
}
public function getClientRequiredFields(): array
{
return [
['name' => 'client_name', 'label' => ctrans('texts.name'), 'type' => 'text', 'validation' => 'required|min:2'],
['name' => 'contact_email', 'label' => ctrans('texts.email'), 'type' => 'text', 'validation' => 'required|email:rfc'],
];
}
public function authorizeView($payment_method)
{
return (new AuthorizePaymentMethod($this))->authorizeView();

View File

@ -69,7 +69,7 @@ class BaseDriver extends AbstractPaymentDriver
/* Array of payment methods */
public static $methods = [];
/** @var array */
public $required_fields = [];
@ -80,6 +80,16 @@ class BaseDriver extends AbstractPaymentDriver
$this->client = $client;
}
/**
* Required fields for client to fill, to proceed with gateway actions.
*
* @return array[]
*/
public function getClientRequiredFields(): array
{
return [];
}
/**
* Authorize a payment method.
*

View File

@ -121,23 +121,11 @@ class CheckoutComPaymentDriver extends BaseDriver
public function authorizeView($data)
{
if (count($this->required_fields) > 0) {
return redirect()
->route('client.profile.edit', ['client_contact' => auth()->user()->hashed_id])
->with('missing_required_fields', $this->required_fields);
}
return $this->payment_method->authorizeView($data);
}
public function authorizeResponse($data)
{
if (count($this->required_fields) > 0) {
return redirect()
->route('client.profile.edit', ['client_contact' => auth()->user()->hashed_id])
->with('missing_required_fields', $this->required_fields);
}
return $this->payment_method->authorizeResponse($data);
}
@ -149,12 +137,6 @@ class CheckoutComPaymentDriver extends BaseDriver
*/
public function processPaymentView(array $data)
{
if (count($this->required_fields) > 0) {
return redirect()
->route('client.profile.edit', ['client_contact' => auth()->user()->hashed_id])
->with('missing_required_fields', $this->required_fields);
}
return $this->payment_method->paymentView($data);
}
@ -166,12 +148,6 @@ class CheckoutComPaymentDriver extends BaseDriver
*/
public function processPaymentResponse($request)
{
if (count($this->required_fields) > 0) {
return redirect()
->route('client.profile.edit', ['client_contact' => auth()->user()->hashed_id])
->with('missing_required_fields', $this->required_fields);
}
return $this->payment_method->paymentResponse($request);
}

View File

@ -80,12 +80,6 @@ class PayPalExpressPaymentDriver extends BaseDriver
public function processPaymentView($data)
{
if (count($this->required_fields) > 0) {
return redirect()
->route('client.profile.edit', ['client_contact' => auth()->user()->hashed_id])
->with('missing_required_fields', $this->required_fields);
}
$this->initializeOmnipayGateway();
$this->payment_hash->data = array_merge((array) $this->payment_hash->data, ['amount' => $data['total']['amount_with_fee']]);
@ -120,12 +114,6 @@ class PayPalExpressPaymentDriver extends BaseDriver
public function processPaymentResponse($request)
{
if (count($this->required_fields) > 0) {
return redirect()
->route('client.profile.edit', ['client_contact' => auth()->user()->hashed_id])
->with('missing_required_fields', $this->required_fields);
}
$this->initializeOmnipayGateway();
$response = $this->omnipay_gateway

View File

@ -149,6 +149,13 @@ class StripePaymentDriver extends BaseDriver
}
}
public function getClientRequiredFields(): array
{
return [
['name' => 'client_postal_code', 'label' => ctrans('texts.postal_code'), 'type' => 'text', 'validation' => 'required'],
];
}
/**
* Proxy method to pass the data into payment method authorizeView().
*
@ -157,12 +164,6 @@ class StripePaymentDriver extends BaseDriver
*/
public function authorizeView(array $data)
{
if (count($this->required_fields) > 0) {
return redirect()
->route('client.profile.edit', ['client_contact' => auth()->user()->hashed_id])
->with('missing_required_fields', $this->required_fields);
}
return $this->payment_method->authorizeView($data);
}
@ -174,12 +175,6 @@ class StripePaymentDriver extends BaseDriver
*/
public function authorizeResponse($request)
{
if (count($this->required_fields) > 0) {
return redirect()
->route('client.profile.edit', ['client_contact' => auth()->user()->hashed_id])
->with('missing_required_fields', $this->required_fields);
}
return $this->payment_method->authorizeResponse($request);
}
@ -191,23 +186,11 @@ class StripePaymentDriver extends BaseDriver
*/
public function processPaymentView(array $data)
{
if (count($this->required_fields) > 0) {
return redirect()
->route('client.profile.edit', ['client_contact' => auth()->user()->hashed_id])
->with('missing_required_fields', $this->required_fields);
}
return $this->payment_method->paymentView($data);
}
public function processPaymentResponse($request) //We never have to worry about unsuccessful payments as failures are handled at the front end for this driver.
{
if (count($this->required_fields) > 0) {
return redirect()
->route('client.profile.edit', ['client_contact' => auth()->user()->hashed_id])
->with('missing_required_fields', $this->required_fields);
}
return $this->payment_method->paymentResponse($request);
}

View File

@ -226,6 +226,7 @@ class EventServiceProvider extends ServiceProvider
],
CreditWasUpdated::class => [
UpdatedCreditActivity::class,
CreateInvoicePdf::class,
],
CreditWasEmailedAndFailed::class => [
],
@ -334,6 +335,7 @@ class EventServiceProvider extends ServiceProvider
],
QuoteWasUpdated::class => [
QuoteUpdatedActivity::class,
CreateInvoicePdf::class,
],
QuoteWasEmailed::class => [
QuoteEmailActivity::class,

View File

@ -95,6 +95,7 @@ class PaymentRepository extends BaseRepository
/*Fill the payment*/
$payment->fill($data);
$payment->is_manual = true;
$payment->status_id = Payment::STATUS_COMPLETED;
$payment->save();

View File

@ -11,6 +11,7 @@
namespace App\Services\Credit;
use App\Factory\ClientContactFactory;
use App\Factory\CreditInvitationFactory;
use App\Models\Credit;
use App\Models\CreditInvitation;
@ -29,6 +30,13 @@ class CreateInvitations extends AbstractService
{
$contacts = $this->credit->client->contacts;
if($contacts->count() == 0){
$this->createBlankContact();
$this->credit->refresh();
$contacts = $this->credit->client->contacts;
}
$contacts->each(function ($contact) {
$invitation = CreditInvitation::whereCompanyId($this->credit->company_id)
->whereClientContactId($contact->id)
@ -47,4 +55,13 @@ class CreateInvitations extends AbstractService
return $this->credit;
}
private function createBlankContact()
{
$new_contact = ClientContactFactory::create($this->credit->company_id, $this->credit->user_id);
$new_contact->client_id = $this->credit->client_id;
$new_contact->contact_key = Str::random(40);
$new_contact->is_primary = true;
$new_contact->save();
}
}

View File

@ -11,6 +11,7 @@
namespace App\Services\Invoice;
use App\Factory\ClientContactFactory;
use App\Factory\InvoiceInvitationFactory;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
@ -27,7 +28,17 @@ class CreateInvitations extends AbstractService
public function run()
{
$this->invoice->client->contacts->each(function ($contact) {
$contacts = $this->invoice->client->contacts;
if($contacts->count() == 0){
$this->createBlankContact();
$this->invoice->refresh();
$contacts = $this->invoice->client->contacts;
}
$contacts->each(function ($contact) {
$invitation = InvoiceInvitation::whereCompanyId($this->invoice->company_id)
->whereClientContactId($contact->id)
->whereInvoiceId($this->invoice->id)
@ -46,4 +57,13 @@ class CreateInvitations extends AbstractService
return $this->invoice;
}
private function createBlankContact()
{
$new_contact = ClientContactFactory::create($this->invoice->company_id, $this->invoice->user_id);
$new_contact->client_id = $this->invoice->client_id;
$new_contact->contact_key = Str::random(40);
$new_contact->is_primary = true;
$new_contact->save();
}
}

View File

@ -66,7 +66,7 @@ class TriggeredActions extends AbstractService
});
if ($this->invoice->invitations->count() > 0) {
event(new InvoiceWasEmailed($this->invoice->invitations->first(), $this->invoice->company, Ninja::eventVars()));
event(new InvoiceWasEmailed($this->invoice->invitations->first(), $this->invoice->company, Ninja::eventVars(), 'invoice'));
}
}
}

View File

@ -11,26 +11,41 @@
namespace App\Services\Quote;
use App\Factory\ClientContactFactory;
use App\Factory\QuoteInvitationFactory;
use App\Models\Quote;
use App\Models\QuoteInvitation;
class CreateInvitations
{
public function __construct()
public $quote;
public function __construct(Quote $quote)
{
$this->quote = $quote;
}
public function run($quote)
public function run()
{
$quote->client->contacts->each(function ($contact) use ($quote) {
$invitation = QuoteInvitation::whereCompanyId($quote->company_id)
$contacts = $this->quote->client->contacts;
if($contacts->count() == 0){
$this->createBlankContact();
$this->quote->refresh();
$contacts = $this->quote->client->contacts;
}
$contacts->each(function ($contact){
$invitation = QuoteInvitation::whereCompanyId($this->quote->company_id)
->whereClientContactId($contact->id)
->whereQuoteId($quote->id)
->whereQuoteId($this->quote->id)
->first();
if (! $invitation && $contact->send_email) {
$ii = QuoteInvitationFactory::create($quote->company_id, $quote->user_id);
$ii->quote_id = $quote->id;
$ii = QuoteInvitationFactory::create($this->quote->company_id, $this->quote->user_id);
$ii->quote_id = $this->quote->id;
$ii->client_contact_id = $contact->id;
$ii->save();
} elseif ($invitation && ! $contact->send_email) {
@ -38,6 +53,16 @@ class CreateInvitations
}
});
return $quote->fresh();
return $this->quote->fresh();
}
private function createBlankContact()
{
$new_contact = ClientContacstFactory::create($this->quote->company_id, $this->quote->user_id);
$new_contact->client_id = $this->quote->client_id;
$new_contact->contact_key = Str::random(40);
$new_contact->is_primary = true;
$new_contact->save();
}
}

View File

@ -34,7 +34,7 @@ class GetQuotePdf extends AbstractService
$invitation = $this->quote->invitations->where('client_contact_id', $this->contact->id)->first();
$path = $this->quote->client->invoice_filepath();
$path = $this->quote->client->quote_filepath();
$file_path = $path.$this->quote->number.'.pdf';

View File

@ -22,7 +22,7 @@ class QuoteService
{
use MakesHash;
protected $quote;
public $quote;
public $invoice;
@ -33,9 +33,7 @@ class QuoteService
public function createInvitations()
{
$create_invitation = new CreateInvitations();
$this->quote = $create_invitation->run($this->quote);
$this->quote = (new CreateInvitations($this->quote))->run();
return $this;
}
@ -58,9 +56,9 @@ class QuoteService
return $this;
}
$convert_quote = new ConvertQuote($this->quote->client);
$convert_quote = (new ConvertQuote($this->quote->client))->run($this->quote);
$this->invoice = $convert_quote->run($this->quote);
$this->invoice = $convert_quote;
$this->quote->fresh();
@ -96,9 +94,7 @@ class QuoteService
public function markSent() :self
{
$mark_sent = new MarkSent($this->quote->client, $this->quote);
$this->quote = $mark_sent->run();
$this->quote = (new MarkSent($this->quote->client, $this->quote))->run();
return $this;
}

View File

@ -43,7 +43,6 @@ class InvoiceTransformer extends EntityTransformer
'terms' => $invoice->terms ?: '',
'public_notes' => $invoice->public_notes ?: '',
'is_deleted' => (bool) $invoice->is_deleted,
'invoice_type_id' => (int) $invoice->invoice_type_id,
'tax_name1' => $invoice->tax_name1 ? $invoice->tax_name1 : '',
'tax_rate1' => (float) $invoice->tax_rate1,
'tax_name2' => $invoice->tax_name2 ? $invoice->tax_name2 : '',

View File

@ -11,10 +11,12 @@
namespace App\Utils\PhantomJS;
use App\Jobs\Util\SystemLogger;
use App\Models\CreditInvitation;
use App\Models\Design;
use App\Models\InvoiceInvitation;
use App\Models\QuoteInvitation;
use App\Models\SystemLog;
use App\Services\PdfMaker\Design as PdfDesignModel;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Services\PdfMaker\PdfMaker as PdfMakerService;
@ -77,7 +79,7 @@ class Phantom
$phantom_url = "https://phantomjscloud.com/api/browser/v2/{$key}/?request=%7Burl:%22{$url}%22,renderType:%22pdf%22%7D";
$pdf = CurlUtils::get($phantom_url);
// Storage::makeDirectory($path, 0775);
$this->checkMime($pdf, $invitation, $entity);
$instance = Storage::disk(config('filesystems.default'))->put($file_path, $pdf);
@ -101,6 +103,36 @@ class Phantom
return $response;
}
/* Check if the returning PDF is valid. */
private function checkMime($pdf, $invitation, $entity)
{
$finfo = new \finfo(FILEINFO_MIME);
if($finfo->buffer($pdf) != 'application/pdf; charset=binary')
{
SystemLogger::dispatch(
$pdf,
SystemLog::CATEGORY_PDF,
SystemLog::EVENT_PDF_RESPONSE,
SystemLog::TYPE_PDF_FAILURE,
$invitation->contact->client
);
}
else {
SystemLogger::dispatch(
"Entity PDF generated sucessfully => " . $invitation->{$entity}->number,
SystemLog::CATEGORY_PDF,
SystemLog::EVENT_PDF_RESPONSE,
SystemLog::TYPE_PDF_SUCCESS,
$invitation->contact->client
);
}
}
public function displayInvitation(string $entity, string $invitation_key)
{
$key = $entity.'_id';

View File

@ -150,11 +150,8 @@ class TemplateEngine
private function entityValues($contact)
{
//$data = $this->entity_obj->buildLabelsAndValues($contact);
$data = (new HtmlEngine($this->entity_obj->invitations->first()))->generateLabelsAndValues();
// $arrKeysLength = array_map('strlen', array_keys($data));
// array_multisort($arrKeysLength, SORT_DESC, $data);
$this->body = strtr($this->body, $data['labels']);
$this->body = strtr($this->body, $data['values']);

View File

@ -21,7 +21,7 @@ trait CleanLineItems
{
public function cleanItems($items) :array
{
if (! isset($items)) {
if (! isset($items) || !is_array($items)) {
return [];
}

View File

@ -22,9 +22,11 @@ trait SavesDocuments
if ($entity instanceof Company) {
$account = $entity->account;
$company = $entity;
$user = auth()->user();
} else {
$account = $entity->company->account;
$company = $entity->company;
$user = $entity->user;
}
if (! $account->hasFeature(Account::FEATURE_DOCUMENTS)) {
@ -35,8 +37,8 @@ trait SavesDocuments
$document = UploadFile::dispatchNow(
$document,
UploadFile::DOCUMENT,
$entity->user,
$entity->company,
$user,
$company,
$entity,
null,
$is_public
@ -49,9 +51,11 @@ trait SavesDocuments
if ($entity instanceof Company) {
$account = $entity->account;
$company = $entity;
$user = auth()->user();
} else {
$account = $entity->company->account;
$company = $entity->company;
$user = $entity->user;
}
if (! $account->hasFeature(Account::FEATURE_DOCUMENTS)) {
@ -61,8 +65,8 @@ trait SavesDocuments
$document = UploadFile::dispatchNow(
$document,
UploadFile::DOCUMENT,
$entity->user,
$entity->company,
$user,
$company,
$entity,
null,
$is_public

View File

@ -13,7 +13,7 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', ''),
'app_version' => '5.0.46',
'app_version' => '5.0.47',
'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', false),

View File

@ -31,7 +31,7 @@ const RESOURCES = {
"assets/fonts/MaterialIcons-Regular.otf": "1288c9e28052e028aba623321f7826ac",
"/": "23224b5e03519aaa87594403d54412cf",
"version.json": "6b7a4ad416a3730ae32b64e007cef7f3",
"main.dart.js": "1cacb64d223fcfa75d0a4838a1fab6f4",
"main.dart.js": "ef2ed4cc194dd16d85ab85db7f0e155f",
"favicon.png": "dca91c54388f52eded692718d5a98b8b"
};

View File

@ -1,2 +1,2 @@
/*! For license information please see authorize-credit-card-payment.js.LICENSE.txt */
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=2)}({2:function(e,t,n){e.exports=n("hK5p")},hK5p:function(e,t){function n(e,t){var n;if("undefined"==typeof Symbol||null==e[Symbol.iterator]){if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return r(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return r(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var o=0,a=function(){};return{s:a,n:function(){return o>=e.length?{done:!0}:{done:!1,value:e[o++]}},e:function(e){throw e},f:a}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var u,c=!0,i=!1;return{s:function(){n=e[Symbol.iterator]()},n:function(){var e=n.next();return c=e.done,e},e:function(e){i=!0,u=e},f:function(){try{c||null==n.return||n.return()}finally{if(i)throw u}}}}function r(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}function o(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}new(function(){function e(t,r){var o=this;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),a(this,"handleAuthorization",(function(){var e=$("#my-card"),t={};t.clientKey=o.publicKey,t.apiLoginID=o.loginId;var n={};n.cardNumber=e.CardJs("cardNumber").replace(/[^\d]/g,""),n.month=e.CardJs("expiryMonth"),n.year=e.CardJs("expiryYear"),n.cardCode=document.getElementById("cvv").value;var r={};return r.authData=t,r.cardData=n,document.getElementById("card_button").disabled=!0,document.querySelector("#card_button > svg").classList.remove("hidden"),document.querySelector("#card_button > span").classList.add("hidden"),Accept.dispatchData(r,o.responseHandler),!1})),a(this,"responseHandler",(function(e){if("Error"===e.messages.resultCode){$("#errors").show().html("<p>"+e.messages.message[0].code+": "+e.messages.message[0].text+"</p>"),document.getElementById("card_button").disabled=!1,document.querySelector("#card_button > svg").classList.add("hidden"),document.querySelector("#card_button > span").classList.remove("hidden")}else"Ok"===e.messages.resultCode&&(document.getElementById("dataDescriptor").value=e.opaqueData.dataDescriptor,document.getElementById("dataValue").value=e.opaqueData.dataValue,document.getElementById("store_card").value=document.getElementById("store_card_checkbox").checked,document.getElementById("server_response").submit());return!1})),a(this,"handle",(function(){if(o.cardButton&&o.cardButton.addEventListener("click",(function(){o.cardButton.disabled=!0,o.handleAuthorization()})),o.payNowButton){var e,t=n(o.payNowButton);try{var r=function(){var t=e.value;t.addEventListener("click",(function(){t.disabled=!0,o.handlePayNowAction(t.dataset.id)}))};for(t.s();!(e=t.n()).done;)r()}catch(e){t.e(e)}finally{t.f()}}return o})),this.publicKey=t,this.loginId=r,this.cardHolderName=document.getElementById("cardholder_name"),this.cardButton=document.getElementById("card_button"),this.payNowButton=document.getElementsByClassName("pay_now_button")}var t,r,u;return t=e,(r=[{key:"handlePayNowAction",value:function(e){document.getElementById("token").value=e,document.getElementById("server_response").submit()}}])&&o(t.prototype,r),u&&o(t,u),e}())(document.querySelector('meta[name="authorize-public-key"]').content,document.querySelector('meta[name="authorize-login-id"]').content).handle()}});
!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/",n(n.s=2)}({2:function(e,t,n){e.exports=n("hK5p")},hK5p:function(e,t){function n(e,t){var n;if("undefined"==typeof Symbol||null==e[Symbol.iterator]){if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return r(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return r(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var o=0,a=function(){};return{s:a,n:function(){return o>=e.length?{done:!0}:{done:!1,value:e[o++]}},e:function(e){throw e},f:a}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var u,c=!0,i=!1;return{s:function(){n=e[Symbol.iterator]()},n:function(){var e=n.next();return c=e.done,e},e:function(e){i=!0,u=e},f:function(){try{c||null==n.return||n.return()}finally{if(i)throw u}}}}function r(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}function o(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}new(function(){function e(t,r){var o=this;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),a(this,"handleAuthorization",(function(){var e=$("#my-card"),t={};t.clientKey=o.publicKey,t.apiLoginID=o.loginId;var n={};n.cardNumber=e.CardJs("cardNumber").replace(/[^\d]/g,""),n.month=e.CardJs("expiryMonth"),n.year=e.CardJs("expiryYear"),n.cardCode=document.getElementById("cvv").value;var r={};return r.authData=t,r.cardData=n,document.getElementById("card_button").disabled=!0,document.querySelector("#card_button > svg").classList.remove("hidden"),document.querySelector("#card_button > span").classList.add("hidden"),Accept.dispatchData(r,o.responseHandler),!1})),a(this,"responseHandler",(function(e){if("Error"===e.messages.resultCode){$("#errors").show().html("<p>"+e.messages.message[0].code+": "+e.messages.message[0].text+"</p>"),document.getElementById("card_button").disabled=!1,document.querySelector("#card_button > svg").classList.add("hidden"),document.querySelector("#card_button > span").classList.remove("hidden")}else if("Ok"===e.messages.resultCode){document.getElementById("dataDescriptor").value=e.opaqueData.dataDescriptor,document.getElementById("dataValue").value=e.opaqueData.dataValue;var t=document.getElementById("store_card_checkbox");t&&(document.getElementById("store_card").value=t.checked),document.getElementById("server_response").submit()}return!1})),a(this,"handle",(function(){if(o.cardButton&&o.cardButton.addEventListener("click",(function(){o.cardButton.disabled=!0,o.handleAuthorization()})),o.payNowButton){var e,t=n(o.payNowButton);try{var r=function(){var t=e.value;t.addEventListener("click",(function(){t.disabled=!0,o.handlePayNowAction(t.dataset.id)}))};for(t.s();!(e=t.n()).done;)r()}catch(e){t.e(e)}finally{t.f()}}return o})),this.publicKey=t,this.loginId=r,this.cardHolderName=document.getElementById("cardholder_name"),this.cardButton=document.getElementById("card_button"),this.payNowButton=document.getElementsByClassName("pay_now_button")}var t,r,u;return t=e,(r=[{key:"handlePayNowAction",value:function(e){document.getElementById("token").value=e,document.getElementById("server_response").submit()}}])&&o(t.prototype,r),u&&o(t,u),e}())(document.querySelector('meta[name="authorize-public-key"]').content,document.querySelector('meta[name="authorize-login-id"]').content).handle()}});

154231
public/main.dart.js vendored

File diff suppressed because one or more lines are too long

View File

@ -4,7 +4,7 @@
"/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/payment_methods/authorize-authorize-card.js": "/js/clients/payment_methods/authorize-authorize-card.js?id=cddcd46c630c71737bda",
"/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js?id=caff3774673a6c683e74",
"/js/clients/payments/authorize-credit-card-payment.js": "/js/clients/payments/authorize-credit-card-payment.js?id=fe43d5a1ad3ec29387d4",
"/js/clients/payments/card-js.min.js": "/js/clients/payments/card-js.min.js?id=5469146cd629ea1b5c20",
"/js/clients/payments/checkout.com.js": "/js/clients/payments/checkout.com.js?id=ce184db42e52d403c21b",
"/js/clients/payments/stripe-ach.js": "/js/clients/payments/stripe-ach.js?id=c4012ad90f17d60432ad",

View File

@ -3,7 +3,7 @@
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -3,7 +3,7 @@
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -3,7 +3,7 @@
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -3,7 +3,7 @@
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
@ -70,9 +70,14 @@ class AuthorizeAuthorizeCard {
document.getElementById("dataDescriptor").value = response.opaqueData.dataDescriptor;
document.getElementById("dataValue").value = response.opaqueData.dataValue;
document.getElementById("store_card").value = document.getElementById("store_card_checkbox").checked;
document.getElementById("server_response").submit();
let store_card_checkbox = document.getElementById("store_card_checkbox");
if (store_card_checkbox) {
document.getElementById("store_card").value = store_card_checkbox.checked;
}
document.getElementById("server_response").submit();
}
return false;

View File

@ -3,7 +3,7 @@
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -3,7 +3,7 @@
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -3,7 +3,7 @@
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -3,7 +3,7 @@
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -3,7 +3,7 @@
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -3,7 +3,7 @@
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -3,7 +3,7 @@
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -3,7 +3,7 @@
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -3,7 +3,7 @@
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -3,7 +3,7 @@
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/

View File

@ -3242,7 +3242,7 @@ return [
'checkout_com' => 'Checkout.com',
'footer_label' => 'Copyright © :year :company. All rights reserved.',
'footer_label' => 'Copyright © :year :company.',
'credit_card_invalid' => 'Provided credit card number is not valid.',
'month_invalid' => 'Provided month is not valid.',
@ -3366,4 +3366,7 @@ return [
'no_action_provided' => 'No action provided. If you believe this is wrong, please contact the support.',
'no_payable_invoices_selected' => 'No payable invoices selected. Make sure you are not trying to pay draft invoice or invoice with zero balance due.',
'required_payment_information' => 'Required payment details',
'required_payment_information_more' => 'To complete a payment we need more details about you.',
];

View File

@ -3,7 +3,7 @@
<a href="{{ $settings->website }}" style="color: #19BB40; text-decoration: underline;">
@endif
<img src="{{ $company->present()->logo() }}" height="50" style="height:50px; max-width:140px; margin-left: 33px; padding-top: 2px" alt=""/>
<img src="{{ $company->present()->logo($settings) }}" height="50" style="height:50px; max-width:140px; margin-left: 33px; padding-top: 2px" alt=""/>
@if ($settings->website)
</a>

View File

@ -1,7 +1,7 @@
@component('email.template.master', ['design' => 'dark', 'settings' => $settings, 'whitelabel' => $whitelabel])
@slot('header')
@component('email.components.header', ['p' => $body, 'logo' => $settings->company_logo ?: 'https://www.invoiceninja.com/wp-content/uploads/2019/01/InvoiceNinja-Logo-Round-300x300.png'])
@component('email.components.header', ['p' => $body, 'logo' => (strlen($settings->company_logo) > 1) ? url('') . $settings->company_logo : 'https://www.invoiceninja.com/wp-content/uploads/2019/01/InvoiceNinja-Logo-Round-300x300.png'])
@if(isset($title))
{{$title}}

View File

@ -1,7 +1,7 @@
@component('email.template.master', ['design' => 'light', 'settings' => $settings, 'whitelabel' => $whitelabel])
@slot('header')
@component('email.components.header', ['p' => $body, 'logo' => $settings->company_logo ?: 'https://www.invoiceninja.com/wp-content/uploads/2019/01/InvoiceNinja-Logo-Round-300x300.png'])
@component('email.components.header', ['p' => $body, 'logo' => (strlen($settings->company_logo) > 1) ? url('') . $settings->company_logo : 'https://www.invoiceninja.com/wp-content/uploads/2019/01/InvoiceNinja-Logo-Round-300x300.png'])
@if(isset($title))
{{$title}}

View File

@ -1,137 +0,0 @@
<body class="app header-fixed sidebar-fixed aside-menu-fixed sidebar-lg-show">
<header class="app-header navbar">
<button class="navbar-toggler sidebar-toggler d-lg-none mr-auto" type="button" data-toggle="sidebar-show">
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand" href="https://invoiceninja.com">
<img class="navbar-brand-full" src="/images/logo.png" width="50" height="50" alt="Invoice Ninja Logo">
<img class="navbar-brand-minimized" src="/images/logo.png" width="30" height="30" alt="Invoice Ninja Logo">
</a>
<button class="sidebar-minimizer brand-minimizer" type="button">
<span class="navbar-toggler-icon"></span>
</button>
<ul class="nav navbar-nav ml-auto">
<li class="nav-item dropdown d-md-down-none" style="padding-left:20px; padding-right: 20px;">
<a class="nav-link" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">
<i class="icon-list"></i>
<span class="badge badge-pill badge-warning">15</span>
</a>
<div class="dropdown-menu dropdown-menu-right dropdown-menu-lg">
<div class="dropdown-header text-center">
<strong>You have 5 pending tasks</strong>
</div>
<a class="dropdown-item" href="#">
<div class="small mb-1">Mr Miyagi todos
<span class="float-right">
<strong>0%</strong>
</span>
</div>
<span class="progress progress-xs">
<div class="progress-bar bg-info" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</span>
</a>
<a class="dropdown-item" href="#">
<div class="small mb-1">First, wash all car.
<span class="float-right">
<strong>25%</strong>
</span>
</div>
<span class="progress progress-xs">
<div class="progress-bar bg-danger" role="progressbar" style="width: 25%" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100"></div>
</span>
</a>
<a class="dropdown-item" href="#">
<div class="small mb-1">Then wax. Wax on...
<span class="float-right">
<strong>50%</strong>
</span>
</div>
<span class="progress progress-xs">
<div class="progress-bar bg-warning" role="progressbar" style="width: 50%" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100"></div>
</span>
</a>
<a class="dropdown-item" href="#">
<div class="small mb-1">No questions!
<span class="float-right">
<strong>75%</strong>
</span>
</div>
<span class="progress progress-xs">
<div class="progress-bar bg-info" role="progressbar" style="width: 75%" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100"></div>
</span>
</a>
<a class="dropdown-item" href="#">
<div class="small mb-1">Wax on... wax off. Wax on... wax off.
<span class="float-right">
<strong>100%</strong>
</span>
</div>
<span class="progress progress-xs">
<div class="progress-bar bg-success" role="progressbar" style="width: 100%" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
</span>
</a>
<a class="dropdown-item text-center" href="#">
<strong>View all tasks</strong>
</a>
</div>
</li>
<li class="nav-item dropdown d-md-down-none" style="padding-left:20px; padding-right: 20px;">
<a class="nav-link" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-building" aria-hidden="true"></i> {{ $current_company->present()->name() }}
</a>
<div class="dropdown-menu dropdown-menu-right dropdown-menu-lg">
<div class="dropdown-header text-center">
<strong>trans('texts.manage_companies')</strong>
</div>
@foreach($companies as $company) <!-- List all remaining companies here-->
<a class="dropdown-item" href="#">
<div class="small mb-1">{{ $company->present()->name }}
<span class="float-right">
</span>
</div>
<span class="progress progress-xs">
<div class="progress-bar bg-info" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</span>
</a>
@endforeach
<div class="dropdown-divider"></div>
<!-- Add Company-->
@if(count($companies) < 5)
<a class="dropdown-item" href="{{ route('user.logout') }}">
<i class="fa fa-plus"></i> trans('texts.add_company')</a>
@endif
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">
<img class="img-avatar" src="/images/logo.png" alt=""> {{ auth()->user()->present()->name }}
</a>
<div class="dropdown-menu dropdown-menu-right">
<div class="dropdown-header text-center">
<strong>Settings</strong>
</div>
<a class="dropdown-item" href="#">
<i class="fa fa-user"></i> Profile</a>
<a class="dropdown-item" href="{{ route('user.settings') }}">
<i class="fa fa-wrench"></i> trans('texts.settings')</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="{{ route('user.logout') }}">
<i class="fa fa-lock"></i> Logout</a>
</div>
</li>
</ul>
<button class="navbar-toggler aside-menu-toggler d-md-down-none" type="button" data-toggle="aside-menu-lg-show">
<span class="navbar-toggler-icon"></span>
</button>
<button class="navbar-toggler aside-menu-toggler d-lg-none" type="button" data-toggle="aside-menu-show">
<span class="navbar-toggler-icon"></span>
</button>
</header>
<div class="app-body">

View File

@ -8,9 +8,9 @@
@endif
@if(strlen($client->getSetting('client_portal_privacy_policy')) > 1 && strlen($client->getSetting('client_portal_terms')) > 1)
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-minus">
<!-- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-minus">
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg> <!-- Long dash between items. -->
</svg> Long dash between items. -->
@endif
@if(strlen($client->getSetting('client_portal_terms')) > 1)

View File

@ -2,7 +2,7 @@
<div class="flex flex-col w-64">
<div class="flex items-center h-16 flex-shrink-0 px-4 bg-primary-darken justify-center">
<a href="{{ route('client.dashboard') }}">
<img class="h-8 w-auto" src="{!! $settings->company_logo ?: asset('images/invoiceninja-white-logo.png') !!}" alt="{{ config('app.name') }}" />
<img class="h-8 w-auto" src="{!! $settings->company_logo ? url('') . $settings->company_logo : asset('images/invoiceninja-white-logo.png') !!}" alt="{{ config('app.name') }}" />
</a>
</div>
<div class="h-0 flex-1 flex flex-col overflow-y-auto">

View File

@ -9,7 +9,7 @@
</button>
</div>
<div class="flex-shrink-0 flex items-center px-4">
<img class="h-6 w-auto" src="{!! $settings->company_logo ?: asset('images/invoiceninja-white-logo.png') !!}" alt="{{ config('app.name') }}" />
<img class="h-6 w-auto" src="{!! url($settings->company_logo) ?: asset('images/invoiceninja-white-logo.png') !!}" alt="{{ config('app.name') }}" />
</div>
<div class="mt-5 flex-1 h-0 overflow-y-auto">
<nav class="flex-1 py-4 bg-primary">

View File

@ -104,8 +104,8 @@
</button>
</form>
@endif
<a href="{{ route('client.invoice.show', $invoice->hashed_id) }}" class="button-link text-primary">
@lang('texts.view')
<a href="{{ route('client.invoice.show', $invoice->hashed_id) }}?mode=fullscreen" class="button-link text-primary">
{{ ctrans('texts.view') }}
</a>
</td>
</tr>
@ -132,4 +132,4 @@
@push('footer')
<script src="{{ asset('js/clients/invoices/action-selectors.js') }}"></script>
@endpush
@endpush

View File

@ -0,0 +1,42 @@
<div class="container mx-auto grid grid-cols-12" data-ref="required-fields-container">
<div class="col-span-12 lg:col-span-6 lg:col-start-4 overflow-hidden bg-white shadow rounded-lg">
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
<h3 class="text-lg font-medium leading-6 text-gray-900">
{{ ctrans('texts.required_payment_information') }}
</h3>
<p class="max-w-2xl mt-1 text-sm leading-5 text-gray-500">
{{ ctrans('texts.required_payment_information_more') }}
</p>
</div>
<form wire:submit.prevent="handleSubmit(Object.fromEntries(new FormData($event.target)))">
@foreach($fields as $field)
@component('portal.ninja2020.components.general.card-element', ['title' => $field['label']])
<input class="input w-full" type="{{ $field['type'] }}" name="{{ $field['name'] }}">
@if(session()->has('validation_errors') && array_key_exists($field['name'], session('validation_errors')))
<p class="mt-2 text-gray-900 border-red-300 px-2 py-1 bg-gray-100">{{ session('validation_errors')[$field['name']][0] }}</p>
@endif
@endcomponent
@endforeach
@component('portal.ninja2020.components.general.card-element-single')
<div class="flex justify-end">
<button class="button button-primary bg-primary">
{{ trans('texts.next_step') }}
</button>
</div>
@endcomponent
</form>
</div>
@if(!$show_form)
<script>
document.addEventListener("DOMContentLoaded", function () {
document.querySelector('div[data-ref="required-fields-container"]').classList.add('hidden');
document.querySelector('div[data-ref="gateway-container"]').classList.remove('hidden');
});
</script>
@endif
</div>

View File

@ -1,6 +1,6 @@
<style>
:root {
--primary-color: {{ $settings->primary_color }};
--primary-color: {{ isset($settings) ? optional($settings)->primary_color : '#1c64f2' }};
}
.bg-primary {
@ -8,11 +8,11 @@
}
.bg-primary-darken {
background-color: vaR(--primary-color);
background-color: var(--primary-color);
filter: brightness(90%);
}
.text-primary {
color: var(--primary-color);
}
</style>
</style>

View File

@ -12,5 +12,5 @@
@endsection
@section('body')
This page is empty, sad and alone.
Coming soon.
@endsection

View File

@ -15,12 +15,15 @@
<div class="mt-2 max-w-xl text-sm leading-5 text-gray-500">
<p translate>
{{ ctrans('texts.invoice_still_unpaid') }}
<!-- This invoice is still not paid. Click the button to complete the payment. -->
</p>
</div>
</div>
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
<div class="inline-flex rounded-md shadow-sm">
<a href="{{ route('client.invoice.show', $invoice->hashed_id) }}" class="mr-4 text-primary">
&#8592; {{ ctrans('texts.client_portal') }}
</a>
<div class="inline-flex items-center rounded-md shadow-sm">
<input type="hidden" name="invoices[]" value="{{ $invoice->hashed_id }}">
<input type="hidden" name="action" value="payment">
<button class="button button-primary bg-primary">{{ ctrans('texts.pay_now') }}</button>
@ -33,4 +36,4 @@
@endif
<iframe src="{{ $invoice->pdf_file_path() }}" class="h-screen w-full border-0"></iframe>
@endsection
@endsection

View File

@ -66,6 +66,8 @@
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/cookieconsent@3/build/cookieconsent.min.css" />
</head>
@include('portal.ninja2020.components.primary-color')
<body class="antialiased {{ $custom_body_class ?? '' }}">
@yield('body')

View File

@ -11,7 +11,9 @@
@endpush
@section('body')
<div class="container mx-auto grid grid-cols-12">
@livewire('required-client-info', ['fields' => $gateway->getClientRequiredFields(), 'contact' => auth('contact')->user()])
<div class="container mx-auto grid grid-cols-12 hidden" data-ref="gateway-container">
<div class="col-span-12 lg:col-span-6 lg:col-start-4 overflow-hidden bg-white shadow rounded-lg">
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
@isset($card_title)
@ -19,6 +21,7 @@
{{ $card_title }}
</h3>
@endisset
@isset($card_description)
<p class="max-w-2xl mt-1 text-sm leading-5 text-gray-500">
{{ $card_description }}
@ -34,4 +37,11 @@
@push('footer')
@yield('gateway_footer')
<script>
Livewire.on('passed-required-fields-check', () => {
document.querySelector('div[data-ref="required-fields-container"]').classList.add('hidden');
document.querySelector('div[data-ref="gateway-container"]').classList.remove('hidden');
});
</script>
@endpush

View File

@ -2,102 +2,112 @@
@section('meta_title', ctrans('texts.payment'))
@section('body')
<div class="container mx-auto">
<div class="overflow-hidden bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
<h3 class="text-lg font-medium leading-6 text-gray-900">
{{ ctrans('texts.payment') }}
</h3>
<p class="max-w-2xl mt-1 text-sm leading-5 text-gray-500" translate>
{{ ctrans('texts.payment_details') }}
</p>
</div>
<div>
<dl>
@if(!empty($payment->clientPaymentDate()) && !is_null($payment->clientPaymentDate()))
<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_date') }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ $payment->clientPaymentDate() }}
</dd>
</div>
@endif
<div class="container mx-auto">
<div class="overflow-hidden bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
<h3 class="text-lg font-medium leading-6 text-gray-900">
{{ ctrans('texts.payment') }}
</h3>
<p class="max-w-2xl mt-1 text-sm leading-5 text-gray-500" translate>
{{ ctrans('texts.payment_details') }}
</p>
</div>
<div>
<dl>
@if(!empty($payment->clientPaymentDate()) && !is_null($payment->clientPaymentDate()))
<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_date') }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ $payment->clientPaymentDate() }}
</dd>
</div>
@endif
@if(!empty($payment->transaction_reference) && !is_null($payment->transaction_reference))
<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.transaction_reference') }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
@if(!empty($payment->transaction_reference) && !is_null($payment->transaction_reference))
<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.transaction_reference') }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<span class="break-all">
{{ $payment->transaction_reference }}
</span>
</dd>
</div>
@endif
</dd>
</div>
@endif
@if(!empty(optional($payment->type)->name) && !is_null(optional($payment->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.method') }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ optional($payment->type)->name }}
</dd>
</div>
@endif
@if(!empty($payment->formattedAmount()) && !is_null($payment->formattedAmount()))
<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.amount') }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ $payment->formattedAmount() }}
</dd>
</div>
@endif
@if(!empty(optional($payment->type)->name) && !is_null(optional($payment->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.method') }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ optional($payment->type)->name }}
</dd>
</div>
@endif
@if(!empty($payment->status_id) && !is_null($payment->status_id))
<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.status') }}
</dt>
<div class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{!! \App\Models\Payment::badgeForStatus($payment->status_id) !!}
</div>
</div>
@endif
</dl>
@if(!empty($payment->formattedAmount()) && !is_null($payment->formattedAmount()))
<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.amount') }}
</dt>
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ $payment->formattedAmount() }}
</dd>
</div>
@endif
@if(!empty($payment->status_id) && !is_null($payment->status_id))
<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.status') }}
</dt>
<div class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{!! \App\Models\Payment::badgeForStatus($payment->status_id) !!}
</div>
</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.refunded') }}
</dt>
<div class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
{{ \App\Utils\Number::formatMoney($payment->refunded, $payment->client) }}
</div>
</div>
@endif
</dl>
</div>
</div>
<div class="mt-4 overflow-hidden bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
<h3 class="text-lg font-medium leading-6 text-gray-900">
{{ ctrans('texts.invoices') }}
</h3>
<p class="max-w-2xl mt-1 text-sm leading-5 text-gray-500">
{{ ctrans('texts.list_of_payment_invoices') }}
</p>
</div>
<div>
<dl>
@foreach($payment->invoices as $invoice)
<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.invoice_number') }}
</dt>
<div class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<a class="button-link text-primary"
href="{{ route('client.invoice.show', ['invoice' => $invoice->hashed_id])}}">
{{ $invoice->number }}
</a>
</div>
</div>
@endforeach
</dl>
</div>
</div>
</div>
<div class="mt-4 overflow-hidden bg-white shadow sm:rounded-lg">
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
<h3 class="text-lg font-medium leading-6 text-gray-900">
{{ ctrans('texts.invoices') }}
</h3>
<p class="max-w-2xl mt-1 text-sm leading-5 text-gray-500">
{{ ctrans('texts.list_of_payment_invoices') }}
</p>
</div>
<div>
<dl>
@foreach($payment->invoices as $invoice)
<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.invoice_number') }}
</dt>
<div class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
<a class="button-link text-primary" href="{{ route('client.invoice.show', ['invoice' => $invoice->hashed_id])}}">
{{ $invoice->number }}
</a>
</div>
</div>
@endforeach
</dl>
</div>
</div>
</div>
@endsection
@endsection

Some files were not shown because too many files have changed in this diff Show More