Merge branch 'v5-develop' into v5-2001-gateways-and-new-tokens

This commit is contained in:
Benjamin Beganović 2021-01-25 16:47:58 +01:00 committed by GitHub
commit 64e6dad248
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
163 changed files with 124211 additions and 120491 deletions

View File

@ -12,7 +12,7 @@ services:
group: deprecated-2017Q4 group: deprecated-2017Q4
php: php:
- 7.3 # - 7.3
- 7.4 - 7.4
# - nightly # - nightly
@ -38,7 +38,7 @@ env:
before_install: before_install:
# set GitHub token and update composer # set GitHub token and update composer
- if [ -n "$GH_TOKEN" ]; then composer config github-oauth.github.com ${GH_TOKEN}; fi; - if [ -n "$GH_TOKEN" ]; then composer config github-oauth.github.com ${GH_TOKEN}; fi;
- composer self-update && composer -V - composer self-update 1.10.19 && composer -V
# - export USE_ZEND_ALLOC=0 # - export USE_ZEND_ALLOC=0
- rvm use 1.9.3 --install --fuzzy - rvm use 1.9.3 --install --fuzzy
- cp .env.travis .env - cp .env.travis .env
@ -77,7 +77,7 @@ before_script:
script: script:
- php ./vendor/bin/phpunit --debug --verbose --coverage-clover=coverage.xml - php ./vendor/bin/phpunit --debug --verbose --coverage-clover=coverage.xml
- php artisan dusk #- php artisan dusk
#- npm test #- npm test
after_success: after_success:
- bash <(curl -s https://codecov.io/bash) - bash <(curl -s https://codecov.io/bash)

View File

@ -1 +1 @@
5.0.50 5.0.53

View File

@ -298,7 +298,7 @@ class CheckData extends Command
$invoice_balance = $client->invoices->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance'); $invoice_balance = $client->invoices->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance');
$credit_balance = $client->credits->where('is_deleted', false)->sum('balance'); $credit_balance = $client->credits->where('is_deleted', false)->sum('balance');
$invoice_balance -= $credit_balance; // $invoice_balance -= $credit_balance;//doesn't make sense to remove the credit amount
$ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first(); $ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first();
@ -388,9 +388,9 @@ class CheckData extends Command
foreach (Client::cursor()->where('is_deleted', 0) as $client) { foreach (Client::cursor()->where('is_deleted', 0) as $client) {
//$invoice_balance = $client->invoices->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance'); //$invoice_balance = $client->invoices->where('is_deleted', false)->where('status_id', '>', 1)->sum('balance');
$invoice_balance = Invoice::where('client_id', $client->id)->where('is_deleted', false)->where('status_id', '>', 1)->withTrashed()->sum('balance'); $invoice_balance = Invoice::where('client_id', $client->id)->where('is_deleted', false)->where('status_id', '>', 1)->withTrashed()->sum('balance');
$client_balance = Credit::where('client_id', $client->id)->where('is_deleted', false)->withTrashed()->sum('balance'); $credit_balance = Credit::where('client_id', $client->id)->where('is_deleted', false)->withTrashed()->sum('balance');
$invoice_balance -= $client_balance; // $invoice_balance -= $credit_balance;
$ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first(); $ledger = CompanyLedger::where('client_id', $client->id)->orderBy('id', 'DESC')->first();

View File

@ -223,7 +223,7 @@ class CreateSingleAccount extends Command
'company_id' => $company->id, 'company_id' => $company->id,
]); ]);
$client->id_number = $this->getNextClientNumber($client); $client->number = $this->getNextClientNumber($client);
$settings = $client->settings; $settings = $client->settings;
$settings->currency_id = "1"; $settings->currency_id = "1";

View File

@ -331,7 +331,7 @@ class CreateTestData extends Command
$this->info('Creating '.$this->count.' clients'); $this->info('Creating '.$this->count.' clients');
for ($x = 0; $x < $this->count * 500; $x++) { for ($x = 0; $x < $this->count * 200; $x++) {
$z = $x + 1; $z = $x + 1;
$this->info('Creating client # '.$z); $this->info('Creating client # '.$z);
@ -400,7 +400,7 @@ class CreateTestData extends Command
'company_id' => $company->id, 'company_id' => $company->id,
]); ]);
$client->id_number = $this->getNextClientNumber($client); $client->number = $this->getNextClientNumber($client);
$settings = $client->settings; $settings = $client->settings;
$settings->currency_id = (string) rand(1, 79); $settings->currency_id = (string) rand(1, 79);

View File

@ -286,7 +286,7 @@ class DemoMode extends Command
'company_id' => $company->id, 'company_id' => $company->id,
]); ]);
$client->id_number = $this->getNextClientNumber($client); $client->number = $this->getNextClientNumber($client);
$settings = $client->settings; $settings = $client->settings;
$settings->currency_id = (string) rand(1, 3); $settings->currency_id = (string) rand(1, 3);
@ -307,6 +307,14 @@ class DemoMode extends Command
'client_id' => $client->id, 'client_id' => $client->id,
'company_id' => $client->company_id, 'company_id' => $client->company_id,
]); ]);
Expense::all()->each(function ($expense){
$expense->number = $this->getNextExpenseNumber($expense);
$expense->save();
});
} }
private function createVendor($client, $assigned_user_id = null) private function createVendor($client, $assigned_user_id = null)
@ -329,15 +337,23 @@ class DemoMode extends Command
'company_id' => $client->company_id, 'company_id' => $client->company_id,
'is_primary' => 0, 'is_primary' => 0,
]); ]);
$vendor->id_number = $this->getNextVendorNumber($vendor);
$vendor->save();
} }
private function createTask($client, $assigned_user_id = null) private function createTask($client, $assigned_user_id = null)
{ {
$vendor = Task::factory()->create([ $task = Task::factory()->create([
'user_id' => $client->user->id, 'user_id' => $client->user->id,
'company_id' => $client->company_id, 'company_id' => $client->company_id,
'client_id' => $client->id 'client_id' => $client->id
]); ]);
$task->number = $this->getNextTaskNumber($task);
$task->save();
} }
private function createProject($client, $assigned_user_id = null) private function createProject($client, $assigned_user_id = null)

View File

@ -155,10 +155,5 @@ class SendTestEmails extends Command
->setSubject($message['subject']) ->setSubject($message['subject'])
->setBody($message['body']); ->setBody($message['body']);
// Mail::to(config('ninja.testvars.test_email'), 'Mr Test')
// ->cc($cc_emails)
// ->bcc($bcc_emails)
//->replyTo(also_available_if_needed)
//->send(new TemplateEmail($email_builder, $user, $client));
} }
} }

View File

@ -15,6 +15,7 @@ use App\Jobs\Cron\RecurringInvoicesCron;
use App\Jobs\Ninja\AdjustEmailQuota; use App\Jobs\Ninja\AdjustEmailQuota;
use App\Jobs\Ninja\CompanySizeCheck; use App\Jobs\Ninja\CompanySizeCheck;
use App\Jobs\Util\ReminderJob; use App\Jobs\Util\ReminderJob;
use App\Jobs\Util\SchedulerCheck;
use App\Jobs\Util\SendFailedEmails; use App\Jobs\Util\SendFailedEmails;
use App\Jobs\Util\UpdateExchangeRates; use App\Jobs\Util\UpdateExchangeRates;
use App\Jobs\Util\VersionCheck; use App\Jobs\Util\VersionCheck;
@ -42,7 +43,6 @@ class Kernel extends ConsoleKernel
protected function schedule(Schedule $schedule) protected function schedule(Schedule $schedule)
{ {
//$schedule->job(new RecurringInvoicesCron)->hourly();
$schedule->job(new VersionCheck)->daily()->withoutOverlapping(); $schedule->job(new VersionCheck)->daily()->withoutOverlapping();
$schedule->command('ninja:check-data')->daily()->withoutOverlapping(); $schedule->command('ninja:check-data')->daily()->withoutOverlapping();
@ -57,13 +57,20 @@ class Kernel extends ConsoleKernel
/* Run hosted specific jobs */ /* Run hosted specific jobs */
if (Ninja::isHosted()) { if (Ninja::isHosted()) {
$schedule->job(new AdjustEmailQuota())->daily()->withoutOverlapping(); $schedule->job(new AdjustEmailQuota())->daily()->withoutOverlapping();
$schedule->job(new SendFailedEmails())->daily()->withoutOverlapping(); $schedule->job(new SendFailedEmails())->daily()->withoutOverlapping();
} }
/* Run queue's in shared hosting with this*/ /* Run queue's with this*/
if (Ninja::isSelfHost()) { if (Ninja::isSelfHost()) {
$schedule->command('queue:work')->everyMinute()->withoutOverlapping(); $schedule->command('queue:work')->everyMinute()->withoutOverlapping();
$schedule->command('queue:restart')->everyFiveMinutes()->withoutOverlapping(); //we need to add this as we are seeing cached queues mess up the system on first load.
//we need to add this as we are seeing cached queues mess up the system on first load.
$schedule->command('queue:restart')->everyFiveMinutes()->withoutOverlapping();
$schedule->job(new SchedulerCheck)->everyFiveMinutes()->withoutOverlapping();
} }
} }

View File

@ -12,7 +12,7 @@
namespace App\Events\Invoice; namespace App\Events\Invoice;
use App\Models\Company; use App\Models\Company;
use App\Models\Invoice; use App\Models\InvoiceInvitation;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
/** /**
@ -22,36 +22,34 @@ class InvoiceWasEmailedAndFailed
{ {
use SerializesModels; use SerializesModels;
/** public $invitation;
* @var Invoice
*/
public $invoice;
/** public $message;
* @var array
*/
public $errors;
public $company; public $company;
public $event_vars; public $event_vars;
public $template;
/** /**
* Create a new event instance. * Create a new event instance.
* *
* @param Invoice $invoice * @param InvoiceInvitation $invitation
* @param Company $company * @param Company $company
* @param string $errors * @param string $errors
* @param array $event_vars * @param array $event_vars
*/ */
public function __construct(Invoice $invoice, Company $company, string $errors, array $event_vars) public function __construct(InvoiceInvitation $invitation, Company $company, string $message, string $template, array $event_vars)
{ {
$this->invoice = $invoice; $this->invitation = $invitation;
$this->company = $company; $this->company = $company;
$this->errors = $errors; $this->message = $message;
$this->event_vars = $event_vars; $this->event_vars = $event_vars;
$this->template = $template;
} }
} }

View File

@ -13,6 +13,7 @@ namespace App\Events\Invoice;
use App\Models\Company; use App\Models\Company;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\Payment;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
/** /**
@ -27,6 +28,8 @@ class InvoiceWasPaid
*/ */
public $invoice; public $invoice;
public $payment;
public $company; public $company;
public $event_vars; public $event_vars;
@ -38,9 +41,10 @@ class InvoiceWasPaid
* @param Company $company * @param Company $company
* @param array $event_vars * @param array $event_vars
*/ */
public function __construct(Invoice $invoice, Company $company, array $event_vars) public function __construct(Invoice $invoice, Payment $payment, Company $company, array $event_vars)
{ {
$this->invoice = $invoice; $this->invoice = $invoice;
$this->payment = $payment;
$this->company = $company; $this->company = $company;
$this->event_vars = $event_vars; $this->event_vars = $event_vars;
} }

View File

@ -11,6 +11,7 @@
namespace App\Factory; namespace App\Factory;
use App\DataMapper\FeesAndLimits;
use App\Models\CompanyGateway; use App\Models\CompanyGateway;
class CompanyGatewayFactory class CompanyGatewayFactory
@ -20,7 +21,8 @@ class CompanyGatewayFactory
$company_gateway = new CompanyGateway; $company_gateway = new CompanyGateway;
$company_gateway->company_id = $company_id; $company_gateway->company_id = $company_id;
$company_gateway->user_id = $user_id; $company_gateway->user_id = $user_id;
// $company_gateway->fees_and_limits = new FeesAndLimits;
return $company_gateway; return $company_gateway;
} }
} }

View File

@ -44,6 +44,7 @@ class InvoiceFactory
$invoice->custom_value4 = 0; $invoice->custom_value4 = 0;
$invoice->amount = 0; $invoice->amount = 0;
$invoice->balance = 0; $invoice->balance = 0;
$invoice->paid_to_date = 0;
$invoice->partial = 0; $invoice->partial = 0;
$invoice->user_id = $user_id; $invoice->user_id = $user_id;
$invoice->company_id = $company_id; $invoice->company_id = $company_id;

View File

@ -50,6 +50,7 @@ class InvoiceToRecurringInvoiceFactory
$recurring_invoice->last_sent_date = null; $recurring_invoice->last_sent_date = null;
$recurring_invoice->next_send_date = null; $recurring_invoice->next_send_date = null;
$recurring_invoice->remaining_cycles = 0; $recurring_invoice->remaining_cycles = 0;
$recurring_invoice->paid_to_date = 0;
return $recurring_invoice; return $recurring_invoice;
} }

View File

@ -45,6 +45,7 @@ class QuoteFactory
$quote->partial = 0; $quote->partial = 0;
$quote->user_id = $user_id; $quote->user_id = $user_id;
$quote->company_id = $company_id; $quote->company_id = $company_id;
$quote->paid_to_date = 0;
return $quote; return $quote;
} }

View File

@ -49,6 +49,7 @@ class RecurringInvoiceFactory
$invoice->last_sent_date = null; $invoice->last_sent_date = null;
$invoice->next_send_date = null; $invoice->next_send_date = null;
$invoice->remaining_cycles = 0; $invoice->remaining_cycles = 0;
$invoice->paid_to_date = 0;
return $invoice; return $invoice;
} }

View File

@ -50,6 +50,7 @@ class RecurringInvoiceToInvoiceFactory
$invoice->recurring_id = $recurring_invoice->id; $invoice->recurring_id = $recurring_invoice->id;
$invoice->client_id = $client->id; $invoice->client_id = $client->id;
$invoice->auto_bill_enabled = $recurring_invoice->auto_bill_enabled; $invoice->auto_bill_enabled = $recurring_invoice->auto_bill_enabled;
$invoice->paid_to_date = 0;
return $invoice; return $invoice;
} }

View File

@ -30,7 +30,7 @@ function nlog($output, $context = []): void
} }
$trace = debug_backtrace(); $trace = debug_backtrace();
\Illuminate\Support\Facades\Log::channel('invoiceninja')->info(print_r($trace[1]['class'],1), []); // \Illuminate\Support\Facades\Log::channel('invoiceninja')->info(print_r($trace[1]['class'],1), []);
\Illuminate\Support\Facades\Log::channel('invoiceninja')->info($output, $context); \Illuminate\Support\Facades\Log::channel('invoiceninja')->info($output, $context);
} }

View File

@ -136,7 +136,7 @@ class ActivityController extends BaseController
$backup = $activity->backup; $backup = $activity->backup;
if (! $backup || ! $backup->html_backup) { if (! $backup || ! $backup->html_backup) {
return response()->json(['message'=> 'No backup exists for this activity', 'errors' => new stdClass], 404); return response()->json(['message'=> ctrans('texts.no_backup_exists'), 'errors' => new stdClass], 404);
} }
$pdf = $this->makePdf(null, null, $backup->html_backup); $pdf = $this->makePdf(null, null, $backup->html_backup);

View File

@ -150,7 +150,7 @@ class BaseController extends Controller
*/ */
public function notFound() public function notFound()
{ {
return response()->json(['message' => '404 | Nothing to see here!'], 404) return response()->json(['message' => ctrans('texts.api_404')], 404)
->header('X-API-VERSION', config('ninja.minimum_client_version')) ->header('X-API-VERSION', config('ninja.minimum_client_version'))
->header('X-APP-VERSION', config('ninja.app_version')); ->header('X-APP-VERSION', config('ninja.app_version'));
} }
@ -198,7 +198,7 @@ class BaseController extends Controller
$updated_at = request()->has('updated_at') ? request()->input('updated_at') : 0; $updated_at = request()->has('updated_at') ? request()->input('updated_at') : 0;
if (auth()->user()->getCompany()->is_large && ! request()->has('updated_at')) { if (auth()->user()->getCompany()->is_large && ! request()->has('updated_at')) {
return response()->json(['message' => 'Cannot load a large account without a updated_at parameter', 'errors' =>[]], 401); return response()->json(['message' => ctrans('texts.large_account_update_parameter'), 'errors' =>[]], 401);
} }
$updated_at = date('Y-m-d H:i:s', $updated_at); $updated_at = date('Y-m-d H:i:s', $updated_at);

View File

@ -151,7 +151,8 @@ class PaymentMethodController extends Controller
event(new MethodDeleted($payment_method, auth('contact')->user()->company, Ninja::eventVars())); event(new MethodDeleted($payment_method, auth('contact')->user()->company, Ninja::eventVars()));
$payment_method->delete(); $payment_method->delete();
} catch (Exception $e) { } catch (Exception $e) {
Log::error(json_encode($e));
nlog($e->getMessage());
return back(); return back();
} }

View File

@ -501,6 +501,6 @@ class CompanyController extends BaseController
} }
} }
return response()->json(['message' => 'success'], 200); return response()->json(['message' => ctrans('texts.success')], 200);
} }
} }

View File

@ -11,6 +11,7 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\DataMapper\FeesAndLimits;
use App\Factory\CompanyGatewayFactory; use App\Factory\CompanyGatewayFactory;
use App\Http\Requests\CompanyGateway\CreateCompanyGatewayRequest; use App\Http\Requests\CompanyGateway\CreateCompanyGatewayRequest;
use App\Http\Requests\CompanyGateway\DestroyCompanyGatewayRequest; use App\Http\Requests\CompanyGateway\DestroyCompanyGatewayRequest;
@ -18,6 +19,7 @@ use App\Http\Requests\CompanyGateway\EditCompanyGatewayRequest;
use App\Http\Requests\CompanyGateway\ShowCompanyGatewayRequest; use App\Http\Requests\CompanyGateway\ShowCompanyGatewayRequest;
use App\Http\Requests\CompanyGateway\StoreCompanyGatewayRequest; use App\Http\Requests\CompanyGateway\StoreCompanyGatewayRequest;
use App\Http\Requests\CompanyGateway\UpdateCompanyGatewayRequest; use App\Http\Requests\CompanyGateway\UpdateCompanyGatewayRequest;
use App\Models\Client;
use App\Models\CompanyGateway; use App\Models\CompanyGateway;
use App\Repositories\CompanyRepository; use App\Repositories\CompanyRepository;
use App\Transformers\CompanyGatewayTransformer; use App\Transformers\CompanyGatewayTransformer;
@ -191,6 +193,18 @@ class CompanyGatewayController extends BaseController
$company_gateway->fill($request->all()); $company_gateway->fill($request->all());
$company_gateway->save(); $company_gateway->save();
/*Always ensure at least one fees and limits object is set per gateway*/
if(!isset($company_gateway->fees_and_limits)) {
$gateway_types = $company_gateway->driver(new Client)->gatewayTypes();
$fees_and_limits = new \stdClass;
$fees_and_limits->{$gateway_types[0]} = new FeesAndLimits;
$company_gateway->fees_and_limits = $fees_and_limits;
$company_gateway->save();
}
return $this->itemResponse($company_gateway); return $this->itemResponse($company_gateway);
} }

View File

@ -115,7 +115,7 @@ class CompanyUserController extends BaseController
$company_user = CompanyUser::whereUserId($user->id)->whereCompanyId($company->id)->first(); $company_user = CompanyUser::whereUserId($user->id)->whereCompanyId($company->id)->first();
if (! $company_user) { if (! $company_user) {
throw new ModelNotFoundException('Company User record not found'); throw new ModelNotFoundException(ctrans('texts.company_user_not_found'));
return; return;
} }

View File

@ -484,7 +484,7 @@ class CreditController extends BaseController
$credits = Credit::withTrashed()->whereIn('id', $this->transformKeys($ids)); $credits = Credit::withTrashed()->whereIn('id', $this->transformKeys($ids));
if (! $credits) { if (! $credits) {
return response()->json(['message' => 'No Credits Found']); return response()->json(['message' => ctrans('texts.no_credits_found')]);
} }
$credits->each(function ($credit, $key) use ($action) { $credits->each(function ($credit, $key) use ($action) {
@ -561,7 +561,7 @@ class CreditController extends BaseController
break; break;
default: default:
return response()->json(['message' => "The requested action `{$action}` is not available."], 400); return response()->json(['message' => ctrans('texts.action_unavailable', ['action' => $action])], 400);
break; break;
} }
} }

View File

@ -124,7 +124,7 @@ class DocumentController extends BaseController
{ {
$this->document_repo->delete($document); $this->document_repo->delete($document);
return response()->json(['message'=>'success']); return response()->json(['message'=> ctrans('texts.success')]);
} }
public function bulk() public function bulk()
@ -136,7 +136,7 @@ class DocumentController extends BaseController
$documents = Document::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get(); $documents = Document::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get();
if (! $invoices) { if (! $invoices) {
return response()->json(['message' => 'No Documents Found']); return response()->json(['message' => ctrans('texts.no_documents_found')]);
} }
/* /*

View File

@ -117,55 +117,55 @@ class EmailController extends BaseController
$subject = $request->input('subject'); $subject = $request->input('subject');
$body = $request->input('body'); $body = $request->input('body');
$entity_string = strtolower(class_basename($entity_obj)); $entity_string = strtolower(class_basename($entity_obj));
$template = $request->input('template'); $template = str_replace("email_template_", "", $request->input('template'));
$template = str_replace("email_template_", "", $template);
$data = [
'subject' => $subject,
'body' => $body
];
$entity_obj->invitations->each(function ($invitation) use ($data, $entity_string, $entity_obj, $template) {
$entity_obj->invitations->each(function ($invitation) use ($subject, $body, $entity_string, $entity_obj, $template) {
if ($invitation->contact->send_email && $invitation->contact->email) { if ($invitation->contact->send_email && $invitation->contact->email) {
$data = [
'subject' => $subject,
'body' => $body
];
$entity_obj->service()->markSent()->save(); $entity_obj->service()->markSent()->save();
//@TODO why is this dispatchNow instead of just dispatch?
//update - changing to dispatch and see if something breaks.
EmailEntity::dispatch($invitation, $invitation->company, $template, $data)->delay(now()->addSeconds(5)); EmailEntity::dispatch($invitation, $invitation->company, $template, $data)->delay(now()->addSeconds(5));
} }
}); });
$entity_obj->last_sent_date = now(); $entity_obj->last_sent_date = now();
$entity_obj->save(); $entity_obj->save();
/*Only notify the admin ONCE, not once per contact/invite*/ /*Only notify the admin ONCE, not once per contact/invite*/
if ($entity_obj instanceof Invoice) { if ($entity_obj instanceof Invoice) {
$this->entity_type = Invoice::class; $this->entity_type = Invoice::class;
$this->entity_transformer = InvoiceTransformer::class; $this->entity_transformer = InvoiceTransformer::class;
if ($entity_obj->invitations->count() >= 1) { if ($entity_obj->invitations->count() >= 1)
$entity_obj->entityEmailEvent($entity_obj->invitations->first(), 'invoice', $template); $entity_obj->entityEmailEvent($entity_obj->invitations->first(), 'invoice', $template);
}
} }
if ($entity_obj instanceof Quote) { if ($entity_obj instanceof Quote) {
$this->entity_type = Quote::class; $this->entity_type = Quote::class;
$this->entity_transformer = QuoteTransformer::class; $this->entity_transformer = QuoteTransformer::class;
if ($entity_obj->invitations->count() >= 1) { if ($entity_obj->invitations->count() >= 1)
event(new QuoteWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars(), 'quote')); event(new QuoteWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars(), 'quote'));
}
} }
if ($entity_obj instanceof Credit) { if ($entity_obj instanceof Credit) {
$this->entity_type = Credit::class; $this->entity_type = Credit::class;
$this->entity_transformer = CreditTransformer::class; $this->entity_transformer = CreditTransformer::class;
if ($entity_obj->invitations->count() >= 1) { if ($entity_obj->invitations->count() >= 1)
event(new CreditWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars(), 'credit')); event(new CreditWasEmailed($entity_obj->invitations->first(), $entity_obj->company, Ninja::eventVars(), 'credit'));
}
} }
if ($entity_obj instanceof RecurringInvoice) { if ($entity_obj instanceof RecurringInvoice) {

View File

@ -481,7 +481,7 @@ class GroupSettingController extends BaseController
$group_settings = GroupSetting::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get(); $group_settings = GroupSetting::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get();
if (! $group_settings) { if (! $group_settings) {
return response()->json(['message' => 'No Group Settings Found']); return response()->json(['message' => ctrans('texts.no_group_settings_found')]);
} }
/* /*

View File

@ -95,7 +95,7 @@ class ImportController extends Controller
{ {
CSVImport::dispatch($request->all(), auth()->user()->company()); CSVImport::dispatch($request->all(), auth()->user()->company());
return response()->json(['message' => 'Importing data, email will be sent on completion'], 200); return response()->json(['message' => ctrans('texts.import_started')], 200);
} }
private function getEntityMap($entity_type) private function getEntityMap($entity_type)

View File

@ -387,7 +387,7 @@ class InvoiceController extends BaseController
} }
if ($invoice->isLocked()) { if ($invoice->isLocked()) {
return response()->json(['message' => 'Invoice is locked, no modifications allowed']); return response()->json(['message' => ctrans('texts.locked_invoice')]);
} }
$invoice = $this->invoice_repo->save($request->all(), $invoice); $invoice = $this->invoice_repo->save($request->all(), $invoice);
@ -526,13 +526,13 @@ class InvoiceController extends BaseController
if ($action == 'download' && $invoices->count() > 1) { if ($action == 'download' && $invoices->count() > 1) {
$invoices->each(function ($invoice) { $invoices->each(function ($invoice) {
if (auth()->user()->cannot('view', $invoice)) { if (auth()->user()->cannot('view', $invoice)) {
return response()->json(['message' => 'Insufficient privileges to access invoice '.$invoice->number]); return response()->json(['message' => ctrans('text.access_denied')]);
} }
}); });
ZipInvoices::dispatch($invoices, $invoices->first()->company, auth()->user()->email); ZipInvoices::dispatch($invoices, $invoices->first()->company, auth()->user()->email);
return response()->json(['message' => 'Email Sent!'], 200); return response()->json(['message' => ctrans('texts.sent_message')], 200);
} }
/* /*
@ -649,7 +649,7 @@ class InvoiceController extends BaseController
break; break;
case 'mark_paid': case 'mark_paid':
if ($invoice->balance < 0 || $invoice->status_id == Invoice::STATUS_PAID || $invoice->is_deleted === true) { if ($invoice->balance < 0 || $invoice->status_id == Invoice::STATUS_PAID || $invoice->is_deleted === true) {
return $this->errorResponse(['message' => 'Invoice cannot be marked as paid'], 400); return $this->errorResponse(['message' => ctrans('texts.invoice_cannot_be_marked_paid')], 400);
} }
$invoice = $invoice->service()->markPaid(); $invoice = $invoice->service()->markPaid();
@ -686,8 +686,6 @@ class InvoiceController extends BaseController
} }
break; break;
case 'delete': case 'delete':
//need to make sure the invoice is cancelled first!!
//$invoice->service()->handleCancellation()->save();
$this->invoice_repo->delete($invoice); $this->invoice_repo->delete($invoice);
@ -711,6 +709,7 @@ class InvoiceController extends BaseController
break; break;
case 'email': case 'email':
//check query parameter for email_type and set the template else use calculateTemplate //check query parameter for email_type and set the template else use calculateTemplate
if (request()->has('email_type') && property_exists($invoice->company->settings, request()->input('email_type'))) { if (request()->has('email_type') && property_exists($invoice->company->settings, request()->input('email_type'))) {
$this->reminder_template = $invoice->client->getSetting(request()->input('email_type')); $this->reminder_template = $invoice->client->getSetting(request()->input('email_type'));
} else { } else {
@ -725,7 +724,7 @@ class InvoiceController extends BaseController
}); });
if ($invoice->invitations->count() >= 1) { if ($invoice->invitations->count() >= 1) {
$invoice->entityEmailEvent($invoice->invitations->first(), $this->reminder_template); $invoice->entityEmailEvent($invoice->invitations->first(), 'invoice', $this->reminder_template);
} }
if (! $bulk) { if (! $bulk) {
@ -734,7 +733,7 @@ class InvoiceController extends BaseController
break; break;
default: default:
return response()->json(['message' => "The requested action `{$action}` is not available."], 400); return response()->json(['message' => ctrans('texts.action_unavailable', ['action' => $action])], 400);
break; break;
} }
} }

View File

@ -134,7 +134,7 @@ class LicenseController extends BaseController
} }
$error = [ $error = [
'message' => 'Invalid license, or invalid environment '.config('ninja.environment'), 'message' => ctrans('texts.invoice_license_or_environment', ['environment' => config('ninja.environment')]),
'errors' => new stdClass, 'errors' => new stdClass,
]; ];

View File

@ -33,6 +33,7 @@
* @OA\Property(property="custom_value4", type="string", example="", description="________"), * @OA\Property(property="custom_value4", type="string", example="", description="________"),
* @OA\Property(property="vat_number", type="string", example="", description="________"), * @OA\Property(property="vat_number", type="string", example="", description="________"),
* @OA\Property(property="id_number", type="string", example="", description="________"), * @OA\Property(property="id_number", type="string", example="", description="________"),
* @OA\Property(property="number", type="string", example="", description="________"),
* @OA\Property(property="shipping_address1", type="string", example="", description="________"), * @OA\Property(property="shipping_address1", type="string", example="", description="________"),
* @OA\Property(property="shipping_address2", type="string", example="", description="________"), * @OA\Property(property="shipping_address2", type="string", example="", description="________"),
* @OA\Property(property="shipping_city", type="string", example="", description="________"), * @OA\Property(property="shipping_city", type="string", example="", description="________"),

View File

@ -30,6 +30,7 @@
* @OA\Property(property="line_items", type="object", example="", description="_________"), * @OA\Property(property="line_items", type="object", example="", description="_________"),
* @OA\Property(property="amount", type="number", format="float", example="10.00", description="_________"), * @OA\Property(property="amount", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="balance", type="number", format="float", example="10.00", description="_________"), * @OA\Property(property="balance", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="paid_to_date", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="discount", type="number", format="float", example="10.00", description="_________"), * @OA\Property(property="discount", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="partial", type="number", format="float", example="10.00", description="_________"), * @OA\Property(property="partial", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="is_amount_discount", type="boolean", example=true, description="_________"), * @OA\Property(property="is_amount_discount", type="boolean", example=true, description="_________"),

View File

@ -29,6 +29,7 @@
* @OA\Property(property="line_items", type="object", example="", description="_________"), * @OA\Property(property="line_items", type="object", example="", description="_________"),
* @OA\Property(property="amount", type="number", format="float", example="10.00", description="_________"), * @OA\Property(property="amount", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="balance", type="number", format="float", example="10.00", description="_________"), * @OA\Property(property="balance", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="paid_to_date", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="discount", type="number", format="float", example="10.00", description="_________"), * @OA\Property(property="discount", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="partial", type="number", format="float", example="10.00", description="_________"), * @OA\Property(property="partial", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="is_amount_discount", type="boolean", example=true, description="_________"), * @OA\Property(property="is_amount_discount", type="boolean", example=true, description="_________"),

View File

@ -29,6 +29,7 @@
* @OA\Property(property="line_items", type="object", example="", description="_________"), * @OA\Property(property="line_items", type="object", example="", description="_________"),
* @OA\Property(property="amount", type="number", format="float", example="10.00", description="_________"), * @OA\Property(property="amount", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="balance", type="number", format="float", example="10.00", description="_________"), * @OA\Property(property="balance", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="paid_to_date", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="discount", type="number", format="float", example="10.00", description="_________"), * @OA\Property(property="discount", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="partial", type="number", format="float", example="10.00", description="_________"), * @OA\Property(property="partial", type="number", format="float", example="10.00", description="_________"),
* @OA\Property(property="is_amount_discount", type="boolean", example=true, description="_________"), * @OA\Property(property="is_amount_discount", type="boolean", example=true, description="_________"),

View File

@ -35,6 +35,7 @@
* @OA\Property(property="custom_value4", type="string", example="", description="________"), * @OA\Property(property="custom_value4", type="string", example="", description="________"),
* @OA\Property(property="vat_number", type="string", example="", description="________"), * @OA\Property(property="vat_number", type="string", example="", description="________"),
* @OA\Property(property="id_number", type="string", example="", description="________"), * @OA\Property(property="id_number", type="string", example="", description="________"),
* @OA\Property(property="number", type="string", example="", description="________"),
* @OA\Property(property="is_deleted", type="boolean", example=true, description="________"), * @OA\Property(property="is_deleted", type="boolean", example=true, description="________"),
* @OA\Property(property="last_login", type="number", format="integer", example="134341234234", description="Timestamp"), * @OA\Property(property="last_login", type="number", format="integer", example="134341234234", description="Timestamp"),
* @OA\Property(property="created_at", type="number", format="integer", example="134341234234", description="Timestamp"), * @OA\Property(property="created_at", type="number", format="integer", example="134341234234", description="Timestamp"),

View File

@ -435,11 +435,6 @@ class PaymentController extends BaseController
*/ */
public function destroy(DestroyPaymentRequest $request, Payment $payment) public function destroy(DestroyPaymentRequest $request, Payment $payment)
{ {
// $payment->service()->deletePayment();
// $payment->is_deleted = true;
// $payment->save();
// $payment->delete();
$this->payment_repo->delete($payment); $this->payment_repo->delete($payment);

View File

@ -74,7 +74,7 @@ class PingController extends BaseController
public function health() public function health()
{ {
if (Ninja::isNinja()) { if (Ninja::isNinja()) {
return response()->json(['message' => 'Route not available', 'errors'=>[]], 403); return response()->json(['message' => ctrans('texts.route_not_available'), 'errors'=>[]], 403);
} }
return response()->json(SystemHealth::check(), 200); return response()->json(SystemHealth::check(), 200);

View File

@ -81,7 +81,7 @@ class PreviewController extends BaseController
$design_object = json_decode(json_encode(request()->input('design'))); $design_object = json_decode(json_encode(request()->input('design')));
if (! is_object($design_object)) { if (! is_object($design_object)) {
return response()->json(['message' => 'Invalid custom design object'], 400); return response()->json(['message' => ctrans('texts.invalid_design_object')], 400);
} }
$entity = ucfirst(request()->input('entity')); $entity = ucfirst(request()->input('entity'));

View File

@ -507,7 +507,7 @@ class QuoteController extends BaseController
$quotes = Quote::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get(); $quotes = Quote::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get();
if (! $quotes) { if (! $quotes) {
return response()->json(['message' => 'No Quote/s Found']); return response()->json(['message' => ctrans('texts.quote_not_found')]);
} }
/* /*
@ -517,13 +517,13 @@ class QuoteController extends BaseController
if ($action == 'download' && $quotes->count() >= 1) { if ($action == 'download' && $quotes->count() >= 1) {
$quotes->each(function ($quote) { $quotes->each(function ($quote) {
if (auth()->user()->cannot('view', $quote)) { if (auth()->user()->cannot('view', $quote)) {
return response()->json(['message'=>'Insufficient privileges to access quote '.$quote->number]); return response()->json(['message'=> ctrans('texts.access_denied')]);
} }
}); });
ZipInvoices::dispatch($quotes, $quotes->first()->company, auth()->user()->email); ZipInvoices::dispatch($quotes, $quotes->first()->company, auth()->user()->email);
return response()->json(['message' => 'Email Sent!'], 200); return response()->json(['message' => ctrans('texts.sent_message')], 200);
} }
if ($action == 'convert') { if ($action == 'convert') {
@ -651,7 +651,7 @@ class QuoteController extends BaseController
case 'approve': case 'approve':
//make sure it hasn't already been approved!! //make sure it hasn't already been approved!!
if ($quote->status_id != Quote::STATUS_SENT) { if ($quote->status_id != Quote::STATUS_SENT) {
return response()->json(['message' => 'Unable to approve this quote as it has expired.'], 400); return response()->json(['message' => ctrans('texts.quote_unapprovable')], 400);
} }
return $this->itemResponse($quote->service()->approve()->save()); return $this->itemResponse($quote->service()->approve()->save());
@ -692,7 +692,7 @@ class QuoteController extends BaseController
case 'email': case 'email':
$quote->service()->sendEmail(); $quote->service()->sendEmail();
return response()->json(['message'=>'email sent'], 200); return response()->json(['message'=> ctrans('texts.sent_message')], 200);
break; break;
case 'mark_sent': case 'mark_sent':
$quote->service()->markSent()->save(); $quote->service()->markSent()->save();
@ -702,7 +702,7 @@ class QuoteController extends BaseController
} }
break; break;
default: default:
return response()->json(['message' => "The requested action `{$action}` is not available."], 400); return response()->json(['message' => ctrans('texts.action_unavailable',['action' => $action])], 400);
break; break;
} }
} }

View File

@ -7,9 +7,9 @@ class SchedulerController extends Controller
public function index() public function index()
{ {
if (auth()->user()->company()->account->latest_version == '0.0.0') { if (auth()->user()->company()->account->latest_version == '0.0.0') {
return response()->json(['message' => 'Scheduler has never run'], 400); return response()->json(['message' => ctrans('texts.scheduler_has_never_run')], 400);
} else { } else {
return response()->json(['message' => 'Scheduler has run'], 200); return response()->json(['message' => ctrans('texts.scheduler_has_run')], 200);
} }
} }
} }

View File

@ -59,7 +59,7 @@ class SelfUpdateController extends BaseController
define('STDIN', fopen('php://stdin', 'r')); define('STDIN', fopen('php://stdin', 'r'));
if (Ninja::isNinja()) { if (Ninja::isNinja()) {
return response()->json(['message' => 'Self update not available on this system.'], 403); return response()->json(['message' => ctrans('texts.self_update_not_available')], 403);
} }
/* .git MUST be owned/writable by the webserver user */ /* .git MUST be owned/writable by the webserver user */

View File

@ -1,101 +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\Request;
/**
* Class SettingsController.
*/
class SettingsController extends BaseController
{
public function __construct()
{
parent::__construct();
}
/**
* Display a listing of the resource.
*
* @return void
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* @return void
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return void
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @param int $id
* @return void
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return void
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @param int $id
* @return void
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return void
*/
public function destroy($id)
{
//
}
}

View File

@ -122,6 +122,7 @@ class SetupController extends Controller
]; ];
try { try {
foreach ($env_values as $property => $value) { foreach ($env_values as $property => $value) {
$this->updateEnvironmentProperty($property, $value); $this->updateEnvironmentProperty($property, $value);
} }

View File

@ -1,123 +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\Request;
use Illuminate\Support\Facades\Cache;
/**
* Class TranslationController.
*/
class TranslationController extends BaseController
{
public function __construct()
{
parent::__construct();
}
/**
* Display a listing of the resource.
*
* @return void
*/
public function index()
{
$strings = Cache::rememberForever('lang.js', function () {
$lang = config('app.locale');
$files = glob(resource_path('lang/'.$lang.'/*.php'));
$strings = [];
foreach ($files as $file) {
$name = basename($file, '.php');
$strings[$name] = require $file;
}
return $strings;
});
header('Content-Type: text/javascript');
echo 'i18n = '.$this->easyMinify(json_encode($strings)).';';
exit();
}
private function easyMinify($javascript)
{
return preg_replace(["/\s+\n/", "/\n\s+/", '/ +/'], ["\n", "\n ", ' '], $javascript);
}
/**
* Show the form for creating a new resource.
*
* @return void
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return void
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @param int $id
* @return void
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return void
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @param int $id
* @return void
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return void
*/
public function destroy($id)
{
//
}
}

View File

@ -668,6 +668,6 @@ class UserController extends BaseController
$company_user->delete(); $company_user->delete();
} }
return response()->json(['message' => 'User detached from company'], 200); return response()->json(['message' => ctrans('texts.user_detached')], 200);
} }
} }

View File

@ -364,7 +364,7 @@ class WebhookController extends BaseController
$webhook->save(); $webhook->save();
if (! $webhook->id) { if (! $webhook->id) {
return response()->json('Failed to create Webhook', 400); return response()->json(ctrans('texts.create_webhook_failure'), 400);
} }
return $this->itemResponse($webhook); return $this->itemResponse($webhook);

View File

@ -65,6 +65,8 @@ class RequiredClientInfo extends Component
public $show_form = false; public $show_form = false;
public function mount() {}
public function handleSubmit(array $data): bool public function handleSubmit(array $data): bool
{ {
$rules = []; $rules = [];

View File

@ -25,13 +25,4 @@ class EditClientRequest extends Request
return auth()->user()->can('edit', $this->client); return auth()->user()->can('edit', $this->client);
} }
// public function prepareForValidation()
// {
// $input = $this->all();
// //$input['id'] = $this->encodePrimaryKey($input['id']);
// $this->replace($input);
// }
} }

View File

@ -25,6 +25,7 @@ class StoreCompanyGatewayRequest extends Request
* *
* @return bool * @return bool
*/ */
public function authorize() : bool public function authorize() : bool
{ {
return auth()->user()->isAdmin(); return auth()->user()->isAdmin();
@ -43,27 +44,32 @@ class StoreCompanyGatewayRequest extends Request
protected function prepareForValidation() protected function prepareForValidation()
{ {
$input = $this->all(); $input = $this->all();
$gateway = Gateway::where('key', $input['gateway_key'])->first(); $gateway = Gateway::where('key', $input['gateway_key'])->first();
$default_gateway_fields = json_decode($gateway->fields); $default_gateway_fields = json_decode($gateway->fields);
/*Force gateway properties */ /*Force gateway properties */
if (isset($input['config']) && is_object(json_decode($input['config']))) { if (isset($input['config']) && is_object(json_decode($input['config']))) {
foreach (json_decode($input['config']) as $key => $value) { foreach (json_decode($input['config']) as $key => $value) {
$default_gateway_fields->{$key} = $value; $default_gateway_fields->{$key} = $value;
} }
$input['config'] = json_encode($default_gateway_fields); $input['config'] = json_encode($default_gateway_fields);
} }
if (isset($input['config'])) { if (isset($input['config']))
$input['config'] = encrypt($input['config']); $input['config'] = encrypt($input['config']);
}
if (isset($input['fees_and_limits']))
if (isset($input['fees_and_limits'])) {
$input['fees_and_limits'] = $this->cleanFeesAndLimits($input['fees_and_limits']); $input['fees_and_limits'] = $this->cleanFeesAndLimits($input['fees_and_limits']);
}
$this->replace($input); $this->replace($input);
} }
} }

View File

@ -32,51 +32,6 @@ class StoreQuoteRequest extends Request
return auth()->user()->can('create', Quote::class); return auth()->user()->can('create', Quote::class);
} }
protected function prepareForValidation()
{
$input = $this->all();
if (array_key_exists('design_id', $input) && is_string($input['design_id'])) {
$input['design_id'] = $this->decodePrimaryKey($input['design_id']);
}
if ($input['client_id']) {
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
}
if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) {
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
}
if (isset($input['client_contacts'])) {
foreach ($input['client_contacts'] as $key => $contact) {
if (! array_key_exists('send_email', $contact) || ! array_key_exists('id', $contact)) {
unset($input['client_contacts'][$key]);
}
}
}
if (isset($input['invitations'])) {
foreach ($input['invitations'] as $key => $value) {
if (isset($input['invitations'][$key]['id']) && is_numeric($input['invitations'][$key]['id'])) {
unset($input['invitations'][$key]['id']);
}
if (isset($input['invitations'][$key]['id']) && is_string($input['invitations'][$key]['id'])) {
$input['invitations'][$key]['id'] = $this->decodePrimaryKey($input['invitations'][$key]['id']);
}
if (is_string($input['invitations'][$key]['client_contact_id'])) {
$input['invitations'][$key]['client_contact_id'] = $this->decodePrimaryKey($input['invitations'][$key]['client_contact_id']);
}
}
}
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
//$input['line_items'] = json_encode($input['line_items']);
$this->replace($input);
}
public function rules() public function rules()
{ {
$rules = []; $rules = [];
@ -98,4 +53,17 @@ class StoreQuoteRequest extends Request
return $rules; return $rules;
} }
protected function prepareForValidation()
{
$input = $this->all();
$input = $this->decodePrimaryKeys($input);
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
$input['amount'] = 0;
$input['balance'] = 0;
$this->replace($input);
}
} }

View File

@ -59,22 +59,12 @@ class UpdateQuoteRequest extends Request
{ {
$input = $this->all(); $input = $this->all();
if (array_key_exists('design_id', $input) && is_string($input['design_id'])) { $input = $this->decodePrimaryKeys($input);
$input['design_id'] = $this->decodePrimaryKey($input['design_id']);
}
if (isset($input['client_id'])) {
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
}
if (isset($input['line_items'])) { if (isset($input['line_items'])) {
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; $input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
} }
if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) {
$input['assigned_user_id'] = $this->decodePrimaryKey($input['assigned_user_id']);
}
if (array_key_exists('documents', $input)) { if (array_key_exists('documents', $input)) {
unset($input['documents']); unset($input['documents']);
} }

View File

@ -33,6 +33,6 @@ class ValidCompanyQuantity implements Rule
*/ */
public function message() public function message()
{ {
return 'Limit of 10 companies per account.'; return ctrans('texts.company_limit_reached');
} }
} }

View File

@ -48,6 +48,6 @@ class CreditsSumRule implements Rule
*/ */
public function message() public function message()
{ {
return "Total credits applied cannot be MORE than total of invoices"; return ctrans('texts.credits_applied_validation');
} }
} }

View File

@ -41,7 +41,7 @@ class UniqueCreditNumberRule implements Rule
*/ */
public function message() public function message()
{ {
return 'Credit number already taken'; return ctrans('texts.credit_number_taken');
} }
/** /**

View File

@ -44,7 +44,8 @@ class ValidCreditsRules implements Rule
private function checkCreditsAreHomogenous() private function checkCreditsAreHomogenous()
{ {
if (! array_key_exists('client_id', $this->input)) { if (! array_key_exists('client_id', $this->input)) {
$this->error_msg = 'Client id is required';
$this->error_msg = ctrans('texts.client_id_required');
return false; return false;
} }
@ -57,44 +58,32 @@ class ValidCreditsRules implements Rule
$cred = Credit::find($this->decodePrimaryKey($credit['credit_id'])); $cred = Credit::find($this->decodePrimaryKey($credit['credit_id']));
if (! $cred) { if (! $cred) {
$this->error_msg = 'Credit not found '; $this->error_msg = ctrans('texts.credit_not_found');
return false; return false;
} }
if ($cred->client_id != $this->input['client_id']) { if ($cred->client_id != $this->input['client_id']) {
$this->error_msg = 'Selected invoices are not from a single client'; $this->error_msg = ctrans('texts.invoices_dont_match_client');
return false; return false;
} }
} }
if (! (array_unique($unique_array) == $unique_array)) { if (! (array_unique($unique_array) == $unique_array)) {
$this->error_msg = 'Duplicate credits submitted.'; $this->error_msg = ctrans('texts.duplicate_credits_submitted');
return false; return false;
} }
if (count($this->input['credits']) >= 1 && count($this->input['invoices']) == 0) { if (count($this->input['credits']) >= 1 && count($this->input['invoices']) == 0) {
$this->error_msg = 'You must have an invoice set when using a credit in a payment'; $this->error_msg = ctrans('texts.credit_with_no_invoice');
return false; return false;
} }
if (count($this->input['credits']) >= 1) { if (count($this->input['credits']) >= 1) {
// $total_payments = $this->input['amount'] + array_sum(array_column($this->input['credits'], 'amount'));
// nlog(print_r($this->input,1));
// nlog("total payments = {$total_payments}");
// nlog("total credits available = " . array_sum(array_column($this->input['credits'], 'amount')));
// nlog("total invoices payable = " . array_sum(array_column($this->input['invoices'], 'amount')));
// if($total_payments > array_sum(array_column($this->input['invoices'], 'amount'))){
// $this->error_msg = "Sum of total payments and credits is greater than the total of invoices";
// return false;
// }
} }
return true; return true;

View File

@ -41,7 +41,7 @@ class UniqueExpenseNumberRule implements Rule
*/ */
public function message() public function message()
{ {
return 'Expense number already taken'; return ctrans('texts.expense_number_taken');
} }
/** /**
@ -57,20 +57,7 @@ class UniqueExpenseNumberRule implements Rule
->where('number', $this->input['number']) ->where('number', $this->input['number'])
->withTrashed(); ->withTrashed();
// if(isset($this->input['client_id']))
// $expense->where('client_id', $this->input['client_id']);
return $expense->exists(); return $expense->exists();
// $expense = Expense::where('client_id', $this->input['client_id'])
// ->where('number', $this->input['number'])
// ->withTrashed()
// ->exists();
// if ($expense) {
// return false;
// }
// return true;
} }
} }

View File

@ -41,7 +41,7 @@ class UniqueInvoiceNumberRule implements Rule
*/ */
public function message() public function message()
{ {
return 'Invoice number already taken'; return ctrans('texts.invoice_number_taken');
} }
/** /**

View File

@ -44,7 +44,8 @@ class ValidInvoicesRules implements Rule
private function checkInvoicesAreHomogenous() private function checkInvoicesAreHomogenous()
{ {
if (! array_key_exists('client_id', $this->input)) { if (! array_key_exists('client_id', $this->input)) {
$this->error_msg = 'Client id is required';
$this->error_msg = ctrans('texts.client_id_required');
return false; return false;
} }
@ -53,25 +54,44 @@ class ValidInvoicesRules implements Rule
//todo optimize this into a single query //todo optimize this into a single query
foreach ($this->input['invoices'] as $invoice) { foreach ($this->input['invoices'] as $invoice) {
$unique_array[] = $invoice['invoice_id']; $unique_array[] = $invoice['invoice_id'];
$inv = Invoice::whereId($invoice['invoice_id'])->first(); $inv = Invoice::whereId($invoice['invoice_id'])->first();
if (! $inv) { if (! $inv) {
$this->error_msg = 'Invoice not found ';
$this->error_msg = ctrans('texts.invoice_not_found');
return false; return false;
} }
if ($inv->client_id != $this->input['client_id']) { if ($inv->client_id != $this->input['client_id']) {
$this->error_msg = 'Selected invoices are not from a single client';
$this->error_msg = ctrans('texts.invoices_dont_match_client');
return false;
}
if($inv->status_id == Invoice::STATUS_DRAFT && $invoice['amount'] <= $inv->amount){
//catch here nothing to do - we need this to prevent the last elseif triggering
}
else if($inv->status_id == Invoice::STATUS_DRAFT && $invoice['amount'] > $inv->amount){
$this->error_msg = 'Amount cannot be greater than invoice balance';
return false;
}
else if($invoice['amount'] > $inv->balance) {
$this->error_msg = ctrans('texts.amount_greater_than_balance');
return false; return false;
} }
} }
if (! (array_unique($unique_array) == $unique_array)) { if (! (array_unique($unique_array) == $unique_array)) {
$this->error_msg = 'Duplicate invoices submitted.'; $this->error_msg = ctrans('texts.duplicate_invoices_submitted');
return false; return false;
} }

View File

@ -41,7 +41,7 @@ class ValidRefundableRequest implements Rule
public function passes($attribute, $value) public function passes($attribute, $value)
{ {
if (! array_key_exists('id', $this->input)) { if (! array_key_exists('id', $this->input)) {
$this->error_msg = 'Payment `id` required.'; $this->error_msg = ctrans('texts.payment_id_required');
return false; return false;
} }
@ -49,7 +49,7 @@ class ValidRefundableRequest implements Rule
$payment = Payment::whereId($this->input['id'])->first(); $payment = Payment::whereId($this->input['id'])->first();
if (! $payment) { if (! $payment) {
$this->error_msg = 'Unable to retrieve specified payment'; $this->error_msg = ctrans('texts.unable_to_retrieve_payment');
return false; return false;
} }
@ -57,31 +57,17 @@ class ValidRefundableRequest implements Rule
$request_invoices = request()->has('invoices') ? $this->input['invoices'] : []; $request_invoices = request()->has('invoices') ? $this->input['invoices'] : [];
$request_credits = request()->has('credits') ? $this->input['credits'] : []; $request_credits = request()->has('credits') ? $this->input['credits'] : [];
// foreach($request_invoices as $key => $value)
// $request_invoices[$key]['invoice_id'] = $this->decodePrimaryKey($value['invoice_id']);
// foreach($request_credits as $key => $value)
// $request_credits[$key]['credit_id'] = $this->decodePrimaryKey($value['credit_id']);
if ($payment->invoices()->exists()) { if ($payment->invoices()->exists()) {
foreach ($payment->invoices as $paymentable_invoice) { foreach ($payment->invoices as $paymentable_invoice) {
$this->checkInvoice($paymentable_invoice, $request_invoices); $this->checkInvoice($paymentable_invoice, $request_invoices);
} }
} }
// if($payment->credits()->exists())
// {
// foreach($payment->credits as $paymentable_credit)
// $this->checkCredit($paymentable_credit, $request_credits);
// }
foreach ($request_invoices as $request_invoice) { foreach ($request_invoices as $request_invoice) {
$this->checkInvoiceIsPaymentable($request_invoice, $payment); $this->checkInvoiceIsPaymentable($request_invoice, $payment);
} }
// foreach($request_credits as $request_credit)
// $this->checkCreditIsPaymentable($request_credit, $payment);
if (strlen($this->error_msg) > 0) { if (strlen($this->error_msg) > 0) {
return false; return false;
} }
@ -97,12 +83,12 @@ class ValidRefundableRequest implements Rule
$paymentable_invoice = $payment->invoices->where('id', $invoice->id)->first(); $paymentable_invoice = $payment->invoices->where('id', $invoice->id)->first();
if (! $paymentable_invoice) { if (! $paymentable_invoice) {
$this->error_msg = 'Invoice id '.$invoice->hashed_id.' is not related to this payment'; $this->error_msg = ctrans('texts.invoice_not_related_to_payment', ['invoice' => $invoice->hashed_id]);
return false; return false;
} }
} else { } else {
$this->error_msg = 'Invoice id '.$invoice->hashed_id.' is not related to this payment'; $this->error_msg = ctrans('texts.invoice_not_related_to_payment', ['invoice' => $invoice->hashed_id]);
return false; return false;
} }
@ -116,12 +102,12 @@ class ValidRefundableRequest implements Rule
$paymentable_credit = $payment->credits->where('id', $credit->id)->first(); $paymentable_credit = $payment->credits->where('id', $credit->id)->first();
if (! $paymentable_invoice) { if (! $paymentable_invoice) {
$this->error_msg = 'Credit id '.$credit->hashed_id.' is not related to this payment'; $this->error_msg = ctrans('texts.credit_not_related_to_payment', ['credit' => $credit->hashed_id]);
return false; return false;
} }
} else { } else {
$this->error_msg = 'Credit id '.$credit->hashed_id.' is not related to this payment'; $this->error_msg = ctrans('texts.credit_not_related_to_payment', ['credit' => $credit->hashed_id]);
return false; return false;
} }
@ -140,7 +126,7 @@ class ValidRefundableRequest implements Rule
if ($request_invoice['amount'] > $refundable_amount) { if ($request_invoice['amount'] > $refundable_amount) {
$invoice = $paymentable; $invoice = $paymentable;
$this->error_msg = 'Attempting to refund more than allowed for invoice id '.$invoice->hashed_id.', maximum refundable amount is '.$refundable_amount; $this->error_msg = ctrans('texts.max_refundable_invoice', ['invoice' => $invoice->hashed_id, 'amount' => $refundable_amount]);
return false; return false;
} }
@ -148,7 +134,7 @@ class ValidRefundableRequest implements Rule
} }
if (! $record_found) { if (! $record_found) {
$this->error_msg = 'Attempting to refund a payment with invoices attached, please specify valid invoice/s to be refunded.'; $this->error_msg = ctrans('texts.refund_without_invoices');
return false; return false;
} }
@ -167,7 +153,7 @@ class ValidRefundableRequest implements Rule
if ($request_credit['amount'] > $refundable_amount) { if ($request_credit['amount'] > $refundable_amount) {
$credit = $paymentable; $credit = $paymentable;
$this->error_msg = 'Attempting to refund more than allowed for credit '.$credit->number.', maximum refundable amount is '.$refundable_amount; $this->error_msg = ctrans('texts.max_refundable_credit',['credit' => $credit->hashed_id, 'amount' => $refundable_amount]);
return false; return false;
} }
@ -175,7 +161,7 @@ class ValidRefundableRequest implements Rule
} }
if (! $record_found) { if (! $record_found) {
$this->error_msg = 'Attempting to refund a payment with credits attached, please specify valid credit/s to be refunded.'; $this->error_msg = ctrans('texts.refund_without_credits');
return false; return false;
} }

View File

@ -33,7 +33,7 @@ class PaymentAmountsBalanceRule implements Rule
*/ */
public function message() public function message()
{ {
return 'Amounts do not balance correctly.'; return ctrans('texts.amounts_do_not_balance');
} }
private function calculateAmounts() :bool private function calculateAmounts() :bool
@ -69,10 +69,8 @@ class PaymentAmountsBalanceRule implements Rule
} }
} else { } else {
return true; return true;
} // if no invoices are present, then this is an unapplied payment, let this pass validation! }
// nlog("payment amounts = {$payment_amounts}");
// nlog("invoice amounts = {$invoice_amounts}");
return $payment_amounts >= $invoice_amounts; return $payment_amounts >= $invoice_amounts;
} }

View File

@ -37,7 +37,7 @@ class PaymentAppliedValidAmount implements Rule
*/ */
public function message() public function message()
{ {
return 'Insufficient applied amount remaining to cover payment.'; return ctrans('texts.insufficient_applied_amount_remaining');
} }
private function calculateAmounts() :bool private function calculateAmounts() :bool

View File

@ -53,6 +53,6 @@ class ValidProjectForClient implements Rule
*/ */
public function message() public function message()
{ {
return "Project client does not match entity client"; return ctrans('texts.project_client_do_not_match');
} }
} }

View File

@ -41,7 +41,7 @@ class UniqueQuoteNumberRule implements Rule
*/ */
public function message() public function message()
{ {
return 'Quote number already taken'; return ctrans('texts.quote_number_taken');
} }
/** /**

View File

@ -41,7 +41,7 @@ class UniqueRecurringInvoiceNumberRule implements Rule
*/ */
public function message() public function message()
{ {
return "Recurring Invoice number {$this->input['number']} already taken"; return ctrans('texts.recurring_invoice_number_taken', ['number' => $this->input['number']]);
} }
/** /**

View File

@ -40,7 +40,7 @@ class RelatedUserRule implements Rule
*/ */
public function message() public function message()
{ {
return 'User not associated with this account'; return ctrans('texts.user_not_associated_with_account');
} }
/** /**

View File

@ -37,26 +37,13 @@ class ValidCreditsPresentRule implements Rule
*/ */
public function message() public function message()
{ {
return 'Insufficient balance on credit.'; return ctrans('texts.insufficient_credit_balance');
} }
private function validCreditsPresent() :bool private function validCreditsPresent() :bool
{ {
//todo need to ensure the clients credits are here not random ones! //todo need to ensure the clients credits are here not random ones!
// if (request()->input('credits') && is_array(request()->input('credits'))) {
// foreach (request()->input('credits') as $credit) {
// $cred = Credit::find($this->decodePrimaryKey($credit['credit_id']));
// if (! $cred || $cred->balance == 0) {
// return false;
// }
// }
// }
// return true;
if (request()->input('credits') && is_array(request()->input('credits'))) { if (request()->input('credits') && is_array(request()->input('credits'))) {
$credit_collection = Credit::whereIn('id', $this->transformKeys(array_column(request()->input('credits'), 'credit_id'))) $credit_collection = Credit::whereIn('id', $this->transformKeys(array_column(request()->input('credits'), 'credit_id')))
->where('balance', '>', 0) ->where('balance', '>', 0)

View File

@ -40,7 +40,7 @@ class ValidPayableInvoicesRule implements Rule
foreach ($invoices as $invoice) { foreach ($invoices as $invoice) {
if (! $invoice->isPayable()) { if (! $invoice->isPayable()) {
$this->error_msg = 'One or more of these invoices have been paid'; $this->error_msg = ctrans('texts.one_or_more_invoices_paid');
return false; return false;
} }

View File

@ -40,7 +40,7 @@ class ValidRefundableInvoices implements Rule
public function passes($attribute, $value) public function passes($attribute, $value)
{ {
if (! array_key_exists('id', $this->input)) { if (! array_key_exists('id', $this->input)) {
$this->error_msg = 'Payment `id` required.'; $this->error_msg = ctrans('texts.payment_id_required');
return false; return false;
} }
@ -48,17 +48,11 @@ class ValidRefundableInvoices implements Rule
$payment = Payment::whereId($this->input['id'])->first(); $payment = Payment::whereId($this->input['id'])->first();
if (! $payment) { if (! $payment) {
$this->error_msg = "Payment couldn't be retrieved cannot be refunded "; $this->error_msg = ctrans('texts.unable_to_retrieve_payment');
return false; return false;
} }
/*We are not sending the Refunded amount in the 'amount field, this is the Payment->amount, need to skip this check. */
// if (request()->has('amount') && (request()->input('amount') > ($payment->amount - $payment->refunded))) {
// $this->error_msg = "Attempting to refund more than payment amount, enter a value equal to or lower than the payment amount of ". $payment->amount;
// return false;
// }
/*If no invoices has been sent, then we apply the payment to the client account*/ /*If no invoices has been sent, then we apply the payment to the client account*/
$invoices = []; $invoices = [];
@ -70,7 +64,7 @@ class ValidRefundableInvoices implements Rule
foreach ($invoices as $invoice) { foreach ($invoices as $invoice) {
if (! $invoice->isRefundable()) { if (! $invoice->isRefundable()) {
$this->error_msg = 'Invoice id '.$invoice->hashed_id.' cannot be refunded'; $this->error_msg = ctrans('texts.invoice_cannot_be_refunded', ['invoice' => $invoice->hashed_id]);
return false; return false;
} }
@ -82,7 +76,7 @@ class ValidRefundableInvoices implements Rule
$pivot_record = $payment->paymentables->where('paymentable_id', $invoice->id)->first(); $pivot_record = $payment->paymentables->where('paymentable_id', $invoice->id)->first();
if ($val['amount'] > ($pivot_record->amount - $pivot_record->refunded)) { if ($val['amount'] > ($pivot_record->amount - $pivot_record->refunded)) {
$this->error_msg = 'Attempting to refund '.$val['amount'].' only '.($pivot_record->amount - $pivot_record->refunded).' available for refund'; $this->error_msg = ctrans('texts.attempted_refund_failed', ['amount' => $val['amount'], 'refundable_amount' => ($pivot_record->amount - $pivot_record->refunded)]);
return false; return false;
} }

View File

@ -34,7 +34,6 @@ class ValidUserForCompany implements Rule
*/ */
public function message() public function message()
{ {
return 'This user is unable to be attached to this company. Perhaps they have already registered a user on another account?'; return ctrans('texts.user_not_associated_with_this_account');
//return ctrans('texts.email_already_register');
} }
} }

View File

@ -78,8 +78,8 @@ class ClientMap
16 => 'texts.custom_value', 16 => 'texts.custom_value',
17 => 'texts.custom_value', 17 => 'texts.custom_value',
18 => 'texts.custom_value', 18 => 'texts.custom_value',
19 => 'texts.address1', 19 => 'texts.shipping_address1',
20 => 'texts.address2', 20 => 'texts.shipping_address2',
21 => 'texts.shipping_city', 21 => 'texts.shipping_city',
22 => 'texts.shipping_state', 22 => 'texts.shipping_state',
23 => 'texts.shipping_postal_code', 23 => 'texts.shipping_postal_code',

View File

@ -57,17 +57,31 @@ class ApplyCreditPayment implements ShouldQueue
if ($cred->id == $this->credit->id) { if ($cred->id == $this->credit->id) {
$cred->pivot->amount = $this->amount; $cred->pivot->amount = $this->amount;
$cred->pivot->save(); $cred->pivot->save();
$cred->paid_to_date += $this->amount;
$cred->save();
} }
}); });
$credit_balance = $this->credit->balance; $credit_balance = $this->credit->balance;
if ($this->amount == $credit_balance) { //total credit applied. if ($this->amount == $credit_balance) { //total credit applied.
$this->credit->setStatus(Credit::STATUS_APPLIED);
$this->credit->updateBalance($this->amount * -1); $this->credit
->service()
->setStatus(Credit::STATUS_APPLIED)
->adjustBalance($this->amount * -1)
->updatePaidToDate($this->amount)
->save();
} elseif ($this->amount < $credit_balance) { //compare number appropriately } elseif ($this->amount < $credit_balance) { //compare number appropriately
$this->credit->setStatus(Credit::STATUS_PARTIAL);
$this->credit->updateBalance($this->amount * -1); $this->credit
->service()
->setStatus(Credit::STATUS_PARTIAL)
->adjustBalance($this->amount * -1)
->updatePaidToDate($this->amount)
->save();
} }
/* Update Payment Applied Amount*/ /* Update Payment Applied Amount*/

View File

@ -15,6 +15,7 @@ use App\Events\Invoice\InvoiceReminderWasEmailed;
use App\Events\Invoice\InvoiceWasEmailed; use App\Events\Invoice\InvoiceWasEmailed;
use App\Events\Invoice\InvoiceWasEmailedAndFailed; use App\Events\Invoice\InvoiceWasEmailedAndFailed;
use App\Jobs\Mail\BaseMailerJob; use App\Jobs\Mail\BaseMailerJob;
use App\Jobs\Mail\EntityFailedSendMailer;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Mail\TemplateEmail; use App\Mail\TemplateEmail;
use App\Models\Activity; use App\Models\Activity;
@ -39,29 +40,32 @@ class EmailEntity extends BaseMailerJob implements ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $invitation; public $invitation; //The entity invitation
public $company; public $company; //The company
public $settings; public $settings; //The settings object
public $entity_string; public $entity_string; //The entity string ie. invoice, quote, credit
public $reminder_template; public $reminder_template; //The base template we are using
public $entity; public $entity; //The entity object
public $html_engine; public $html_engine; //The HTMLEngine object
public $email_entity_builder; public $email_entity_builder; //The email builder which merges the template and text
public $template_data; public $template_data; //The data to be merged into the template
/** /**
* EmailEntity constructor. * EmailEntity constructor.
*
*
* @param Invitation $invitation * @param Invitation $invitation
* @param Company $company * @param Company $company
* @param ?string $reminder_template * @param ?string $reminder_template
* @param array $template_data
*/ */
public function __construct($invitation, Company $company, ?string $reminder_template = null, $template_data = null) public function __construct($invitation, Company $company, ?string $reminder_template = null, $template_data = null)
{ {
@ -92,12 +96,14 @@ class EmailEntity extends BaseMailerJob implements ShouldQueue
*/ */
public function handle() public function handle()
{ {
if ($this->company->is_disabled) { /* Don't fire emails if the company is disabled */
if ($this->company->is_disabled)
return true; return true;
}
/* Set DB */
MultiDB::setDB($this->company->db); MultiDB::setDB($this->company->db);
/* Set the correct mail driver */
$this->setMailDriver(); $this->setMailDriver();
try { try {
@ -105,12 +111,10 @@ class EmailEntity extends BaseMailerJob implements ShouldQueue
->send( ->send(
new TemplateEmail( new TemplateEmail(
$this->email_entity_builder, $this->email_entity_builder,
$this->invitation->contact->user,
$this->invitation->contact->client $this->invitation->contact->client
) )
); );
} catch (\Exception $e) { } catch (\Exception $e) {
$this->entityEmailFailed($e->getMessage()); $this->entityEmailFailed($e->getMessage());
$this->logMailError($e->getMessage(), $this->entity->client); $this->logMailError($e->getMessage(), $this->entity->client);
} }
@ -132,11 +136,12 @@ class EmailEntity extends BaseMailerJob implements ShouldQueue
} }
} }
/* Switch statement to handling failure notifications */
private function entityEmailFailed($message) private function entityEmailFailed($message)
{ {
switch ($this->entity_string) { switch ($this->entity_string) {
case 'invoice': case 'invoice':
event(new InvoiceWasEmailedAndFailed($this->invitation->invoice, $this->company, $message, Ninja::eventVars())); event(new InvoiceWasEmailedAndFailed($this->invitation, $this->company, $message, $this->reminder_template, Ninja::eventVars()));
break; break;
default: default:
@ -145,30 +150,7 @@ class EmailEntity extends BaseMailerJob implements ShouldQueue
} }
} }
// private function entityEmailSucceeded() /* Builds the email builder object */
// {
// switch ($this->reminder_template) {
// case 'invoice':
// event(new InvoiceWasEmailed($this->invitation, $this->company, Ninja::eventVars()));
// break;
// case 'reminder1':
// event(new InvoiceReminderWasEmailed($this->invitation, $this->company, Ninja::eventVars(), Activity::INVOICE_REMINDER1_SENT));
// break;
// case 'reminder2':
// event(new InvoiceReminderWasEmailed($this->invitation, $this->company, Ninja::eventVars(), Activity::INVOICE_REMINDER2_SENT));
// break;
// case 'reminder3':
// event(new InvoiceReminderWasEmailed($this->invitation, $this->company, Ninja::eventVars(), Activity::INVOICE_REMINDER3_SENT));
// break;
// case 'reminder_endless':
// event(new InvoiceReminderWasEmailed($this->invitation, $this->company, Ninja::eventVars(), Activity::INVOICE_REMINDER_ENDLESS_SENT));
// break;
// default:
// # code...
// break;
// }
// }
private function resolveEmailBuilder() private function resolveEmailBuilder()
{ {
$class = 'App\Mail\Engine\\' . ucfirst(Str::camel($this->entity_string)) . "EmailEngine"; $class = 'App\Mail\Engine\\' . ucfirst(Str::camel($this->entity_string)) . "EmailEngine";

View File

@ -96,7 +96,7 @@ class ZipInvoices extends BaseMailerJob implements ShouldQueue
Mail::to($this->email) Mail::to($this->email)
->send(new DownloadInvoices(Storage::disk(config('filesystems.default'))->url($path.$file_name), $this->company)); ->send(new DownloadInvoices(Storage::disk(config('filesystems.default'))->url($path.$file_name), $this->company));
} catch (\Exception $e) { } catch (\Exception $e) {
$this->failed($e); // //$this->failed($e);
} }
UnlinkFile::dispatch(config('filesystems.default'), $path.$file_name)->delay(now()->addHours(1)); UnlinkFile::dispatch(config('filesystems.default'), $path.$file_name)->delay(now()->addHours(1));

View File

@ -12,6 +12,7 @@
namespace App\Jobs\Mail; namespace App\Jobs\Mail;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Mail\Admin\EntityFailedSendObject;
use App\Mail\Admin\EntityNotificationMailer; use App\Mail\Admin\EntityNotificationMailer;
use App\Mail\Admin\EntitySentObject; use App\Mail\Admin\EntitySentObject;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
@ -40,6 +41,8 @@ class EntityFailedSendMailer extends BaseMailerJob implements ShouldQueue
public $settings; public $settings;
public $template; public $template;
public $message;
/** /**
* Create a new job instance. * Create a new job instance.
* *
@ -48,7 +51,7 @@ class EntityFailedSendMailer extends BaseMailerJob implements ShouldQueue
* @param $user * @param $user
* @param $company * @param $company
*/ */
public function __construct($invitation, $entity_type, $user, $company, $template) public function __construct($invitation, $entity_type, $user, $company, $template, $message)
{ {
$this->company = $company; $this->company = $company;
@ -63,6 +66,8 @@ class EntityFailedSendMailer extends BaseMailerJob implements ShouldQueue
$this->settings = $invitation->contact->client->getMergedSettings(); $this->settings = $invitation->contact->client->getMergedSettings();
$this->template = $template; $this->template = $template;
$this->message = $message;
} }
/** /**
@ -72,12 +77,10 @@ class EntityFailedSendMailer extends BaseMailerJob implements ShouldQueue
*/ */
public function handle() public function handle()
{ {
nlog("entity sent mailer");
/*If we are migrating data we don't want to fire these notification*/ /*If we are migrating data we don't want to fire these notification*/
if ($this->company->is_disabled) { if ($this->company->is_disabled)
return true; return true;
}
//Set DB //Set DB
MultiDB::setDb($this->company->db); MultiDB::setDb($this->company->db);
@ -85,14 +88,15 @@ class EntityFailedSendMailer extends BaseMailerJob implements ShouldQueue
//if we need to set an email driver do it now //if we need to set an email driver do it now
$this->setMailDriver(); $this->setMailDriver();
$mail_obj = (new EntitySentObject($this->invitation, $this->entity_type, $this->template))->build(); $mail_obj = (new EntityFailedSendObject($this->invitation, $this->entity_type, $this->template, $this->message))->build();
$mail_obj->from = [config('mail.from.address'), config('mail.from.name')]; $mail_obj->from = [config('mail.from.address'), config('mail.from.name')];
try { try {
Mail::to($this->user->email) Mail::to($this->user->email)
->send(new EntityNotificationMailer($mail_obj)); ->send(new EntityNotificationMailer($mail_obj));
} catch (\Exception $e) { } catch (\Exception $e) {
$this->failed($e); nlog("failing in EntityFailedSendMailer");
//$this->failed($e);
$this->logMailError($e->getMessage(), $this->entity->client); $this->logMailError($e->getMessage(), $this->entity->client);
} }
} }

View File

@ -83,7 +83,7 @@ class EntityPaidMailer extends BaseMailerJob implements ShouldQueue
Mail::to($this->user->email) Mail::to($this->user->email)
->send(new EntityNotificationMailer($mail_obj)); ->send(new EntityNotificationMailer($mail_obj));
} catch (\Exception $e) { } catch (\Exception $e) {
$this->failed($e); // //$this->failed($e);
$this->logMailError($e->getMessage(), $this->payment->client); $this->logMailError($e->getMessage(), $this->payment->client);
} }
} }

View File

@ -72,12 +72,10 @@ class EntitySentMailer extends BaseMailerJob implements ShouldQueue
*/ */
public function handle() public function handle()
{ {
nlog("entity sent mailer");
/*If we are migrating data we don't want to fire these notification*/ /*If we are migrating data we don't want to fire these notification*/
if ($this->company->is_disabled) { if ($this->company->is_disabled)
return true; return true;
}
//Set DB //Set DB
MultiDB::setDb($this->company->db); MultiDB::setDb($this->company->db);
@ -92,7 +90,7 @@ class EntitySentMailer extends BaseMailerJob implements ShouldQueue
Mail::to($this->user->email) Mail::to($this->user->email)
->send(new EntityNotificationMailer($mail_obj)); ->send(new EntityNotificationMailer($mail_obj));
} catch (\Exception $e) { } catch (\Exception $e) {
$this->failed($e); // //$this->failed($e);
$this->logMailError($e->getMessage(), $this->entity->client); $this->logMailError($e->getMessage(), $this->entity->client);
} }
} }

View File

@ -88,7 +88,7 @@ class EntityViewedMailer extends BaseMailerJob implements ShouldQueue
Mail::to($this->user->email) Mail::to($this->user->email)
->send(new EntityNotificationMailer($mail_obj)); ->send(new EntityNotificationMailer($mail_obj));
} catch (\Exception $e) { } catch (\Exception $e) {
$this->failed($e); //$this->failed($e);
$this->logMailError($e->getMessage(), $this->entity->client); $this->logMailError($e->getMessage(), $this->entity->client);
} }
} }

View File

@ -73,7 +73,7 @@ class MailRouter extends BaseMailerJob implements ShouldQueue
Mail::to($this->to_user->email) Mail::to($this->to_user->email)
->send($this->mailable); ->send($this->mailable);
} catch (\Exception $e) { } catch (\Exception $e) {
$this->failed($e); //$this->failed($e);
if ($this->to_user instanceof ClientContact) { if ($this->to_user instanceof ClientContact) {
$this->logMailError($e->getMessage(), $this->to_user->client); $this->logMailError($e->getMessage(), $this->to_user->client);

View File

@ -98,7 +98,7 @@ class PaymentFailureMailer extends BaseMailerJob implements ShouldQueue
Mail::to($company_user->user->email) Mail::to($company_user->user->email)
->send(new EntityNotificationMailer($mail_obj)); ->send(new EntityNotificationMailer($mail_obj));
} catch (\Exception $e) { } catch (\Exception $e) {
$this->failed($e); //$this->failed($e);
$this->logMailError($e->getMessage(), $this->client); $this->logMailError($e->getMessage(), $this->client);
} }
} }

View File

@ -308,7 +308,7 @@ class SendReminders implements ShouldQueue
$invoice = $invoice->calc()->getInvoice(); $invoice = $invoice->calc()->getInvoice();
$this->invoice->client->service()->updateBalance($this->invoice->balance - $temp_invoice_balance)->save(); $this->invoice->client->service()->updateBalance($this->invoice->balance - $temp_invoice_balance)->save();
$this->invoice->ledger()->updateInvoiceBalance($this->invoice->balance - $temp_invoice_balance); $this->invoice->ledger()->updateInvoiceBalance($this->invoice->balance - $temp_invoice_balance, "Late Fee Adjustment for invoice {$this->invoice->number}");
return $invoice; return $invoice;
} }

View File

@ -80,11 +80,11 @@ class EmailPayment extends BaseMailerJob implements ShouldQueue
try { try {
$mail = Mail::to($this->contact->email, $this->contact->present()->name()); $mail = Mail::to($this->contact->email, $this->contact->present()->name());
$mail->send(new TemplateEmail($email_builder, $this->contact->user, $this->contact->client)); $mail->send(new TemplateEmail($email_builder, $this->contact->client));
} catch (\Exception $e) { } catch (\Exception $e) {
nlog("mailing failed with message " . $e->getMessage()); nlog("mailing failed with message " . $e->getMessage());
event(new PaymentWasEmailedAndFailed($this->payment, $this->company, Mail::failures(), Ninja::eventVars())); event(new PaymentWasEmailedAndFailed($this->payment, $this->company, Mail::failures(), Ninja::eventVars()));
$this->failed($e); //$this->failed($e);
return $this->logMailError($e->getMessage(), $this->payment->client); return $this->logMailError($e->getMessage(), $this->payment->client);
} }

View File

@ -54,7 +54,18 @@ class UpdateOrCreateProduct implements ShouldQueue
public function handle() public function handle()
{ {
MultiDB::setDB($this->company->db); MultiDB::setDB($this->company->db);
/*
* If the invoice was generated from a Task or Expense then
* we do NOT update the product details this short block we
* check for the presence of a task_id and/or expense_id
*/
$expense_count = count(array_column((array)$this->products, 'expense_id'));
$task_count = count(array_column((array)$this->products, 'task_id'));
if($task_count >= 1 || $expense_count >= 1)
return;
//only update / create products - not tasks or gateway fees //only update / create products - not tasks or gateway fees
$updateable_products = collect($this->products)->filter(function ($item) { $updateable_products = collect($this->products)->filter(function ($item) {
return $item->type_id == 1; return $item->type_id == 1;

View File

@ -79,7 +79,7 @@ class UserEmailChanged extends BaseMailerJob implements ShouldQueue
Mail::to($this->new_email) Mail::to($this->new_email)
->send(new UserNotificationMailer($mail_obj)); ->send(new UserNotificationMailer($mail_obj));
} catch (\Exception $e) { } catch (\Exception $e) {
$this->failed($e); //$this->failed($e);
$this->logMailError($e->getMessage(), $this->company->owner()); $this->logMailError($e->getMessage(), $this->company->owner());
} }
} }

View File

@ -896,14 +896,6 @@ class Import implements ShouldQueue
], ],
]; ];
//depending on the status, we do a final action.
//s$payment = $this->updatePaymentForStatus($payment, $modified['status_id']);
// if($modified['is_deleted'])
// $payment->service()->deletePayment();
// if(isset($modified['deleted_at']))
// $payment->delete();
} }
Payment::reguard(); Payment::reguard();

View File

@ -0,0 +1,70 @@
<?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\Jobs\Util;
use App\Models\Account;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Artisan;
class SchedulerCheck implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct()
{
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
set_time_limit(0);
Account::whereNotNull('id')->update(['is_scheduler_running' => true]);
if(config('ninja.app_version') != base_path('VERSION.txt'))
{
try {
Artisan::call('migrate', ['--force' => true]);
} catch (\Exception $e) {
nlog("I wasn't able to migrate the data.");
}
try {
Artisan::call('optimize');
} catch (\Exception $e) {
nlog("I wasn't able to optimize.");
}
try {
Artisan::call('view:clear');
} catch (\Exception $e) {
nlog("I wasn't able to clear the views.");
}
VersionCheck::dispatch();
}
}
}

View File

@ -49,6 +49,6 @@ class CreditArchivedActivity implements ShouldQueue
$fields->company_id = $event->credit->company_id; $fields->company_id = $event->credit->company_id;
$fields->activity_type_id = Activity::ARCHIVE_CREDIT; $fields->activity_type_id = Activity::ARCHIVE_CREDIT;
$this->activity_repo->save($fields, $$event->credit, $event->event_vars); $this->activity_repo->save($fields, $event->credit, $event->event_vars);
} }
} }

View File

@ -39,17 +39,19 @@ class InvoiceEmailFailedActivity implements ShouldQueue
*/ */
public function handle($event) public function handle($event)
{ {
nlog("inside activity_repo");
MultiDB::setDb($event->company->db); MultiDB::setDb($event->company->db);
$fields = new stdClass; $fields = new stdClass;
$fields->invoice_id = $event->invoice->id; $fields->invoice_id = $event->invitation->invoice->id;
$fields->client_id = $event->invoice->client_id; $fields->client_id = $event->invitation->invoice->client_id;
$fields->user_id = $event->invoice->user_id; $fields->user_id = $event->invitation->invoice->user_id;
$fields->company_id = $event->invoice->company_id; $fields->company_id = $event->invitation->invoice->company_id;
$fields->activity_type_id = Activity::EMAIL_INVOICE_FAILED; $fields->activity_type_id = Activity::EMAIL_INVOICE_FAILED;
$fields->notes = $event->errors; $fields->notes = $event->message;
$this->activity_repo->save($fields, $event->invoice, $event->event_vars); $this->activity_repo->save($fields, $event->invitation->invoice, $event->event_vars);
} }
} }

View File

@ -41,22 +41,32 @@ class InvoiceEmailedNotification implements ShouldQueue
$invoice->last_sent_date = now(); $invoice->last_sent_date = now();
$invoice->save(); $invoice->save();
/* We loop through each user and determine whether they need to be notified */
foreach ($event->invitation->company->company_users as $company_user) { foreach ($event->invitation->company->company_users as $company_user) {
/* The User */
$user = $company_user->user; $user = $company_user->user;
/* This is only here to handle the alternate message channels - ie Slack */
$notification = new EntitySentNotification($event->invitation, 'invoice'); $notification = new EntitySentNotification($event->invitation, 'invoice');
/* Returns an array of notification methods */
$methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'invoice', ['all_notifications', 'invoice_sent']); $methods = $this->findUserNotificationTypes($event->invitation, $company_user, 'invoice', ['all_notifications', 'invoice_sent']);
/* If one of the methods is email then we fire the EntitySentMailer */
if (($key = array_search('mail', $methods)) !== false && $first_notification_sent === true) { if (($key = array_search('mail', $methods)) !== false && $first_notification_sent === true) {
unset($methods[$key]); unset($methods[$key]);
EntitySentMailer::dispatch($event->invitation, 'invoice', $user, $event->invitation->company, $event->template); EntitySentMailer::dispatch($event->invitation, 'invoice', $user, $event->invitation->company, $event->template);
/* This prevents more than one notification being sent */
$first_notification_sent = false; $first_notification_sent = false;
} }
/* Override the methods in the Notification Class */
$notification->method = $methods; $notification->method = $methods;
/* Notify on the alternate channels */
$user->notify($notification); $user->notify($notification);
} }
} }

View File

@ -11,6 +11,7 @@
namespace App\Listeners\Invoice; namespace App\Listeners\Invoice;
use App\Jobs\Mail\EntityFailedSendMailer;
use App\Jobs\Mail\EntitySentMailer; use App\Jobs\Mail\EntitySentMailer;
use App\Libraries\MultiDB; use App\Libraries\MultiDB;
use App\Notifications\Admin\EntitySentNotification; use App\Notifications\Admin\EntitySentNotification;
@ -33,6 +34,8 @@ class InvoiceFailedEmailNotification implements ShouldQueue
*/ */
public function handle($event) public function handle($event)
{ {
nlog("inside a failed notification");
MultiDB::setDb($event->company->db); MultiDB::setDb($event->company->db);
$first_notification_sent = true; $first_notification_sent = true;
@ -51,7 +54,7 @@ class InvoiceFailedEmailNotification implements ShouldQueue
if (($key = array_search('mail', $methods)) !== false && $first_notification_sent === true) { if (($key = array_search('mail', $methods)) !== false && $first_notification_sent === true) {
unset($methods[$key]); unset($methods[$key]);
EntitySentMailer::dispatch($event->invitation, 'invoice', $user, $event->invitation->company, $event->template); EntityFailedSendMailer::dispatch($event->invitation, 'invoice', $user, $event->invitation->company, $event->template, $event->message);
$first_notification_sent = false; $first_notification_sent = false;
} }

View File

@ -47,7 +47,8 @@ class InvoicePaidActivity implements ShouldQueue
$fields->user_id = $event->invoice->user_id; $fields->user_id = $event->invoice->user_id;
$fields->company_id = $event->invoice->company_id; $fields->company_id = $event->invoice->company_id;
$fields->activity_type_id = Activity::PAID_INVOICE; $fields->activity_type_id = Activity::PAID_INVOICE;
$fields->payment_id = $event->payment->id;
$this->activity_repo->save($fields, $event->invoice, $event->event_vars); $this->activity_repo->save($fields, $event->invoice, $event->event_vars);
try { try {

View File

@ -34,7 +34,9 @@ class EntityFailedSendObject
private $template_body; private $template_body;
public function __construct($invitation, $entity_type, $template) private $message;
public function __construct($invitation, $entity_type, $template, $message)
{ {
$this->invitation = $invitation; $this->invitation = $invitation;
$this->entity_type = $entity_type; $this->entity_type = $entity_type;
@ -42,6 +44,7 @@ class EntityFailedSendObject
$this->contact = $invitation->contact; $this->contact = $invitation->contact;
$this->company = $invitation->company; $this->company = $invitation->company;
$this->template = $template; $this->template = $template;
$this->message = $message;
} }
public function build() public function build()
@ -127,6 +130,7 @@ class EntityFailedSendObject
'amount' => $this->getAmount(), 'amount' => $this->getAmount(),
'client' => $this->contact->present()->name(), 'client' => $this->contact->present()->name(),
'invoice' => $this->entity->number, 'invoice' => $this->entity->number,
'error' => $this->message,
] ]
), ),
'url' => $this->invitation->getAdminLink(), 'url' => $this->invitation->getAdminLink(),

View File

@ -34,6 +34,7 @@ class EntityNotificationMailer extends Mailable
*/ */
public function build() public function build()
{ {
return $this->from(config('mail.from.address'), config('mail.from.name')) return $this->from(config('mail.from.address'), config('mail.from.name'))
->subject($this->mail_obj->subject) ->subject($this->mail_obj->subject)
->markdown($this->mail_obj->markdown, $this->mail_obj->data) ->markdown($this->mail_obj->markdown, $this->mail_obj->data)

View File

@ -91,6 +91,7 @@ class EntitySentObject
$this->template_subject = "texts.notification_credit_sent_subject"; $this->template_subject = "texts.notification_credit_sent_subject";
$this->template_body = "texts.notification_credit_sent"; $this->template_body = "texts.notification_credit_sent";
break; break;
default: default:
$this->template_subject = "texts.notification_invoice_sent_subject"; $this->template_subject = "texts.notification_invoice_sent_subject";
$this->template_body = "texts.notification_invoice_sent"; $this->template_body = "texts.notification_invoice_sent";
@ -115,20 +116,25 @@ class EntitySentObject
); );
} }
private function getData() private function getMessage()
{ {
$settings = $this->entity->client->getMergedSettings(); return ctrans(
return [
'title' => $this->getSubject(),
'message' => ctrans(
$this->template_body, $this->template_body,
[ [
'amount' => $this->getAmount(), 'amount' => $this->getAmount(),
'client' => $this->contact->present()->name(), 'client' => $this->contact->present()->name(),
'invoice' => $this->entity->number, 'invoice' => $this->entity->number,
] ]
), );
}
private function getData()
{
$settings = $this->entity->client->getMergedSettings();
return [
'title' => $this->getSubject(),
'message' => $this->getMessage(),
'url' => $this->invitation->getAdminLink(), 'url' => $this->invitation->getAdminLink(),
'button' => ctrans("texts.view_{$this->entity_type}"), 'button' => ctrans("texts.view_{$this->entity_type}"),
'signature' => $settings->email_signature, 'signature' => $settings->email_signature,

View File

@ -123,6 +123,7 @@ class PaymentEmailEngine extends BaseEmailEngine
$data['$address1'] = ['value' => $this->client->address1 ?: '&nbsp;', 'label' => ctrans('texts.address1')]; $data['$address1'] = ['value' => $this->client->address1 ?: '&nbsp;', 'label' => ctrans('texts.address1')];
$data['$address2'] = ['value' => $this->client->address2 ?: '&nbsp;', 'label' => ctrans('texts.address2')]; $data['$address2'] = ['value' => $this->client->address2 ?: '&nbsp;', 'label' => ctrans('texts.address2')];
$data['$id_number'] = ['value' => $this->client->id_number ?: '&nbsp;', 'label' => ctrans('texts.id_number')]; $data['$id_number'] = ['value' => $this->client->id_number ?: '&nbsp;', 'label' => ctrans('texts.id_number')];
$data['$client.number'] = ['value' => $this->client->number ?: '&nbsp;', 'label' => ctrans('texts.number')];
$data['$vat_number'] = ['value' => $this->client->vat_number ?: '&nbsp;', 'label' => ctrans('texts.vat_number')]; $data['$vat_number'] = ['value' => $this->client->vat_number ?: '&nbsp;', 'label' => ctrans('texts.vat_number')];
$data['$website'] = ['value' => $this->client->present()->website() ?: '&nbsp;', 'label' => ctrans('texts.website')]; $data['$website'] = ['value' => $this->client->present()->website() ?: '&nbsp;', 'label' => ctrans('texts.website')];
$data['$phone'] = ['value' => $this->client->present()->phone() ?: '&nbsp;', 'label' => ctrans('texts.phone')]; $data['$phone'] = ['value' => $this->client->present()->phone() ?: '&nbsp;', 'label' => ctrans('texts.phone')];

View File

@ -23,18 +23,12 @@ class TemplateEmail extends Mailable
private $build_email; private $build_email;
private $user; //the user the email will be sent from
private $client; private $client;
private $footer; public function __construct($build_email, Client $client)
public function __construct($build_email, User $user, Client $client)
{ {
$this->build_email = $build_email; $this->build_email = $build_email;
$this->user = $user; //this is inappropriate here, need to refactor 'user' in this context the 'user' could also be the 'system'
$this->client = $client; $this->client = $client;
} }

View File

@ -73,7 +73,8 @@ class Client extends BaseModel implements HasLocalePreference
'id_number', 'id_number',
'group_settings_id', 'group_settings_id',
'public_notes', 'public_notes',
'phone' 'phone',
'number',
]; ];
protected $with = [ protected $with = [

View File

@ -49,10 +49,7 @@ class ClientContact extends Authenticatable implements HasLocalePreference
'hashed_id', 'hashed_id',
]; ];
protected $with = [ protected $with = [];
// 'client',
// 'company'
];
protected $casts = [ protected $casts = [
'updated_at' => 'timestamp', 'updated_at' => 'timestamp',
@ -87,12 +84,6 @@ class ClientContact extends Authenticatable implements HasLocalePreference
'client_id', 'client_id',
]; ];
/* Changing the username to id allows us to login() a contact that doesn't have an email address set*/
public function username()
{
return 'id';
}
public function getEntityType() public function getEntityType()
{ {
return self::class; return self::class;

View File

@ -108,7 +108,6 @@ class CompanyGateway extends BaseModel
private function driver_class() private function driver_class()
{ {
$class = 'App\\PaymentDrivers\\'.$this->gateway->provider.'PaymentDriver'; $class = 'App\\PaymentDrivers\\'.$this->gateway->provider.'PaymentDriver';
//$class = str_replace('\\', '', $class);
$class = str_replace('_', '', $class); $class = str_replace('_', '', $class);
if (class_exists($class)) { if (class_exists($class)) {

View File

@ -261,4 +261,5 @@ class Credit extends BaseModel
} }
}); });
} }
} }

View File

@ -90,4 +90,14 @@ class CompanyPresenter extends EntityPresenter
return false; return false;
} }
} }
public function getSpcQrCode($client_custom, $invoice_number, $balance)
{
$settings = $this->entity->settings;
return
"SPC\n0200\n1\nCH860021421411198240K\nK\n{$this->name}\n{$settings->address1}\n{$settings->postal_code} {$settings->city}\n\n\nCH\n\n\n\n\n\n\n\n{$balance}\n{$client_custom}\n\n\n\n\n\n\n\nNON\n\n{$invoice_number}\nEPD\n";
}
} }

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