mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
commit
829722d102
@ -1 +1 @@
|
||||
5.3.31
|
||||
5.3.32
|
106
app/Console/Commands/TranslationsExport.php
Normal file
106
app/Console/Commands/TranslationsExport.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Backup;
|
||||
use App\Models\Design;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Lang;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use stdClass;
|
||||
|
||||
class TranslationsExport extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'ninja:translations';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Transform translations to json';
|
||||
|
||||
private array $langs = [
|
||||
'ar',
|
||||
'ca',
|
||||
'cs',
|
||||
'da',
|
||||
'de',
|
||||
'el',
|
||||
'en',
|
||||
'en_GB',
|
||||
'es',
|
||||
'es_ES',
|
||||
'fa',
|
||||
'fi',
|
||||
'fr',
|
||||
'fr_CA',
|
||||
'hr',
|
||||
'it',
|
||||
'ja',
|
||||
'lt',
|
||||
'lv_LV',
|
||||
'mk_MK',
|
||||
'nb_NO',
|
||||
'nl',
|
||||
'pl',
|
||||
'pt_BR',
|
||||
'pt_PT',
|
||||
'ro',
|
||||
'ru_RU',
|
||||
'sl',
|
||||
'sq',
|
||||
'sv',
|
||||
'th',
|
||||
'tr_TR',
|
||||
'zh_TW'
|
||||
];
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
Storage::makeDirectory(storage_path('lang'));
|
||||
|
||||
foreach($this->langs as $lang)
|
||||
{
|
||||
Storage::makeDirectory(storage_path("lang/{$lang}"));
|
||||
|
||||
$translations = Lang::getLoader()->load($lang,'texts');
|
||||
|
||||
Storage::put(storage_path("lang/{$lang}/{$lang}.json"), json_encode(Arr::dot($translations), JSON_UNESCAPED_UNICODE));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -28,7 +28,7 @@ class ClientFactory
|
||||
$client->public_notes = '';
|
||||
$client->balance = 0;
|
||||
$client->paid_to_date = 0;
|
||||
$client->country_id = 4;
|
||||
$client->country_id = 840;
|
||||
$client->is_deleted = 0;
|
||||
$client->client_hash = Str::random(40);
|
||||
$client->settings = ClientSettings::defaults();
|
||||
|
@ -70,7 +70,7 @@ class GmailTransport extends Transport
|
||||
if($child->getContentType() != 'text/plain')
|
||||
{
|
||||
|
||||
$this->gmail->attach(TempFile::filePath($child->getBody(), $child->getHeaders()->get('Content-Type')->getParameter('name') ));
|
||||
$this->gmail->attach(TempFile::filePath($child->getBody(), $child->getHeaders()->get('Content-Type')->getParameter('name') ));
|
||||
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ class ContactForgotPasswordController extends Controller
|
||||
*/
|
||||
public function showLinkRequestForm(Request $request)
|
||||
{
|
||||
$account_id = $request->get('account_id');
|
||||
$account_id = $request->has('account_id') ? $request->get('account_id') : 1;
|
||||
$account = Account::find($account_id);
|
||||
$company = $account->companies->first();
|
||||
|
||||
|
@ -36,12 +36,9 @@ class ContactLoginController extends Controller
|
||||
|
||||
public function showLoginForm(Request $request)
|
||||
{
|
||||
//if we are on the root domain invoicing.co do not show any company logos
|
||||
// if(Ninja::isHosted() && count(explode('.', request()->getHost())) == 2){
|
||||
// $company = null;
|
||||
// }else
|
||||
|
||||
$company = false;
|
||||
$account = false;
|
||||
|
||||
if($request->has('company_key')){
|
||||
MultiDB::findAndSetDbByCompanyKey($request->input('company_key'));
|
||||
@ -65,13 +62,16 @@ class ContactLoginController extends Controller
|
||||
|
||||
}
|
||||
elseif (Ninja::isSelfHost()) {
|
||||
$company = Account::first()->default_company;
|
||||
$account = Account::first();
|
||||
$company = $account->default_company;
|
||||
} else {
|
||||
$company = null;
|
||||
}
|
||||
|
||||
$account_id = $request->get('account_id');
|
||||
$account = Account::find($account_id);
|
||||
if(!$account){
|
||||
$account_id = $request->get('account_id');
|
||||
$account = Account::find($account_id);
|
||||
}
|
||||
|
||||
return $this->render('auth.login', ['account' => $account, 'company' => $company]);
|
||||
|
||||
|
@ -68,7 +68,7 @@ class ContactResetPasswordController extends Controller
|
||||
*/
|
||||
public function showResetForm(Request $request, $token = null)
|
||||
{
|
||||
$account_id = $request->get('account_id');
|
||||
$account_id = $request->has('account_id') ? $request->get('account_id') : 1;
|
||||
$account = Account::find($account_id);
|
||||
$db = $account->companies->first()->db;
|
||||
$company = $account->companies->first();
|
||||
|
@ -334,7 +334,7 @@ class BaseController extends Controller
|
||||
|
||||
},
|
||||
'company.expense_categories'=> function ($query) use ($updated_at, $user) {
|
||||
$query->where('updated_at', '>=', $updated_at);
|
||||
$query->whereNotNull('updated_at');
|
||||
},
|
||||
'company.task_statuses'=> function ($query) use ($updated_at, $user) {
|
||||
$query->whereNotNull('updated_at');
|
||||
@ -568,7 +568,7 @@ class BaseController extends Controller
|
||||
|
||||
},
|
||||
'company.expense_categories'=> function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at);
|
||||
$query->whereNotNull('created_at');
|
||||
|
||||
},
|
||||
'company.task_statuses'=> function ($query) use ($created_at, $user) {
|
||||
|
@ -29,7 +29,9 @@ class SubscriptionController extends Controller
|
||||
->where('client_id', auth('contact')->user()->client->id)
|
||||
->where('company_id', auth('contact')->user()->client->company_id)
|
||||
->where('status_id', RecurringInvoice::STATUS_ACTIVE)
|
||||
->where('is_deleted', 0)
|
||||
->whereNotNull('subscription_id')
|
||||
->withTrashed()
|
||||
->count();
|
||||
|
||||
if($count == 0)
|
||||
|
@ -200,6 +200,7 @@ class CreditController extends BaseController
|
||||
|
||||
$credit = $credit->service()
|
||||
->fillDefaults()
|
||||
->triggeredActions($request)
|
||||
->save();
|
||||
|
||||
event(new CreditWasCreated($credit, $credit->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
@ -377,7 +378,9 @@ class CreditController extends BaseController
|
||||
|
||||
$credit = $this->credit_repository->save($request->all(), $credit);
|
||||
|
||||
$credit->service()->deletePdf();
|
||||
$credit->service()
|
||||
->triggeredActions($request)
|
||||
->deletePdf();
|
||||
|
||||
event(new CreditWasUpdated($credit, $credit->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
|
@ -174,12 +174,7 @@ class PreviewController extends BaseController
|
||||
|
||||
MultiDB::setDb($company->db);
|
||||
|
||||
if($request->input('entity') == 'invoice'){
|
||||
$repo = new InvoiceRepository();
|
||||
$entity_obj = InvoiceFactory::create($company->id, auth()->user()->id);
|
||||
$class = Invoice::class;
|
||||
}
|
||||
elseif($request->input('entity') == 'quote'){
|
||||
if($request->input('entity') == 'quote'){
|
||||
$repo = new QuoteRepository();
|
||||
$entity_obj = QuoteFactory::create($company->id, auth()->user()->id);
|
||||
$class = Quote::class;
|
||||
@ -195,7 +190,11 @@ class PreviewController extends BaseController
|
||||
$entity_obj = RecurringInvoiceFactory::create($company->id, auth()->user()->id);
|
||||
$class = RecurringInvoice::class;
|
||||
}
|
||||
|
||||
else { //assume it is either an invoice or a null object
|
||||
$repo = new InvoiceRepository();
|
||||
$entity_obj = InvoiceFactory::create($company->id, auth()->user()->id);
|
||||
$class = Invoice::class;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
|
@ -189,6 +189,9 @@ class BillingPortalPurchase extends Component
|
||||
$this->coupon = request()->query('coupon');
|
||||
$this->handleCoupon();
|
||||
}
|
||||
elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0){
|
||||
$this->price = $this->subscription->promo_price;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -141,7 +141,7 @@ class RequiredClientInfo extends Component
|
||||
$_field = $this->mappings[$field['name']];
|
||||
|
||||
if (Str::startsWith($field['name'], 'client_')) {
|
||||
if (empty($this->contact->client->{$_field}) || is_null($this->contact->client->{$_field})) {
|
||||
if (empty($this->contact->client->{$_field}) || is_null($this->contact->client->{$_field}) || $this->contact->client->{$_field} = 840) {
|
||||
$this->show_form = true;
|
||||
} else {
|
||||
$this->fields[$index]['filled'] = true;
|
||||
@ -149,7 +149,7 @@ class RequiredClientInfo extends Component
|
||||
}
|
||||
|
||||
if (Str::startsWith($field['name'], 'contact_')) {
|
||||
if ((empty($this->contact->{$_field}) || is_null($this->contact->{$_field}))) {
|
||||
if ((empty($this->contact->{$_field}) || is_null($this->contact->{$_field})) || $this->contact->client->{$_field} = 840) {
|
||||
$this->show_form = true;
|
||||
} else {
|
||||
$this->fields[$index]['filled'] = true;
|
||||
|
@ -36,6 +36,7 @@ class TasksTable extends Component
|
||||
{
|
||||
$query = Task::query()
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', false)
|
||||
->where('client_id', auth('contact')->user()->client->id);
|
||||
|
||||
if ($this->company->getSetting('show_all_tasks_client_portal') === 'invoiced') {
|
||||
|
@ -84,6 +84,8 @@ class WepaySignup extends Component
|
||||
|
||||
public function submit()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$data = $this->validate($this->rules);
|
||||
|
||||
//need to create or get a new WePay CompanyGateway
|
||||
|
@ -34,12 +34,12 @@ class CreateAccountRequest extends Request
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//'email' => 'required|string|email|max:100',
|
||||
'first_name' => 'string|max:100',
|
||||
'last_name' => 'string:max:100',
|
||||
'password' => 'required|string|min:6',
|
||||
'email' => 'bail|required|email:rfc,dns',
|
||||
'email' => new NewUniqueUserRule(),
|
||||
// 'email' => 'bail|required|email:rfc,dns',
|
||||
// 'email' => new NewUniqueUserRule(),
|
||||
'email' => ['required', 'email:rfc,dns', new NewUniqueUserRule],
|
||||
'privacy_policy' => 'required|boolean',
|
||||
'terms_of_service' => 'required|boolean',
|
||||
];
|
||||
|
@ -28,7 +28,8 @@ class ShowDocumentRequest extends FormRequest
|
||||
public function authorize()
|
||||
{
|
||||
return auth()->user('contact')->client->id == $this->document->documentable_id
|
||||
|| $this->document->documentable->client_id == auth()->user('contact')->client->id;
|
||||
|| $this->document->documentable->client_id == auth()->user('contact')->client->id
|
||||
|| $this->document->company_id == auth()->user('contact')->company->id;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,6 +69,7 @@ class ApplyCreditPayment implements ShouldQueue
|
||||
|
||||
$this->credit
|
||||
->service()
|
||||
->markSent()
|
||||
->setStatus(Credit::STATUS_APPLIED)
|
||||
->adjustBalance($this->amount * -1)
|
||||
->updatePaidToDate($this->amount)
|
||||
@ -78,6 +79,7 @@ class ApplyCreditPayment implements ShouldQueue
|
||||
|
||||
$this->credit
|
||||
->service()
|
||||
->markSent()
|
||||
->setStatus(Credit::STATUS_PARTIAL)
|
||||
->adjustBalance($this->amount * -1)
|
||||
->updatePaidToDate($this->amount)
|
||||
|
86
app/Listeners/Quote/QuoteApprovedNotification.php
Normal file
86
app/Listeners/Quote/QuoteApprovedNotification.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* Quote Ninja (https://quoteninja.com).
|
||||
*
|
||||
* @link https://github.com/quoteninja/quoteninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Quote Ninja LLC (https://quoteninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Listeners\Quote;
|
||||
|
||||
use App\Jobs\Mail\NinjaMailer;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Mail\Admin\EntityCreatedObject;
|
||||
use App\Mail\Admin\QuoteApprovedObject;
|
||||
use App\Notifications\Admin\EntitySentNotification;
|
||||
use App\Utils\Traits\Notifications\UserNotifies;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
class QuoteApprovedNotification implements ShouldQueue
|
||||
{
|
||||
use UserNotifies;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
MultiDB::setDb($event->company->db);
|
||||
|
||||
$first_notification_sent = true;
|
||||
|
||||
$quote = $event->quote;
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new NinjaMailer( (new QuoteApprovedObject($quote, $event->company))->build() );
|
||||
$nmo->company = $quote->company;
|
||||
$nmo->settings = $quote->company->settings;
|
||||
|
||||
/* We loop through each user and determine whether they need to be notified */
|
||||
foreach ($event->company->company_users as $company_user) {
|
||||
|
||||
/* The User */
|
||||
$user = $company_user->user;
|
||||
|
||||
if(!$user)
|
||||
continue;
|
||||
|
||||
/* This is only here to handle the alternate message channels - ie Slack */
|
||||
// $notification = new EntitySentNotification($event->invitation, 'quote');
|
||||
|
||||
/* Returns an array of notification methods */
|
||||
$methods = $this->findUserNotificationTypes($quote->invitations()->first(), $company_user, 'quote', ['all_notifications', 'quote_approved', 'quote_approved_all']);
|
||||
|
||||
/* If one of the methods is email then we fire the EntitySentMailer */
|
||||
if (($key = array_search('mail', $methods)) !== false) {
|
||||
unset($methods[$key]);
|
||||
|
||||
|
||||
$nmo->to_user = $user;
|
||||
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
/* This prevents more than one notification being sent */
|
||||
$first_notification_sent = false;
|
||||
}
|
||||
|
||||
/* Override the methods in the Notification Class */
|
||||
// $notification->method = $methods;
|
||||
|
||||
// Notify on the alternate channels
|
||||
// $user->notify($notification);
|
||||
}
|
||||
}
|
||||
}
|
@ -69,7 +69,7 @@ class ClientPaymentFailureObject
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
$this->invoices = Invoice::whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->get();
|
||||
$this->invoices = Invoice::withTrashed()->whereIn('id', $this->transformKeys(array_column($this->payment_hash->invoices(), 'invoice_id')))->get();
|
||||
|
||||
$mail_obj = new stdClass;
|
||||
$mail_obj->amount = $this->getAmount();
|
||||
@ -101,8 +101,13 @@ class ClientPaymentFailureObject
|
||||
|
||||
private function getData()
|
||||
{
|
||||
$invitation = $this->invoices->first()->invitations->first();
|
||||
|
||||
if(!$invitation)
|
||||
throw new \Exception('Unable to find invitation for reference');
|
||||
|
||||
$signature = $this->client->getSetting('email_signature');
|
||||
$html_variables = (new HtmlEngine($this->invoices->first()->invitations->first()))->makeValues();
|
||||
$html_variables = (new HtmlEngine($invitation))->makeValues();
|
||||
$signature = str_replace(array_keys($html_variables), array_values($html_variables), $signature);
|
||||
|
||||
$data = [
|
||||
|
103
app/Mail/Admin/QuoteApprovedObject.php
Normal file
103
app/Mail/Admin/QuoteApprovedObject.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Mail\Admin;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\Quote;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use stdClass;
|
||||
|
||||
class QuoteApprovedObject
|
||||
{
|
||||
|
||||
public $quote;
|
||||
|
||||
public $company;
|
||||
|
||||
public $settings;
|
||||
|
||||
public function __construct(Quote $quote, Company $company)
|
||||
{
|
||||
$this->quote = $quote;
|
||||
$this->company = $company;
|
||||
}
|
||||
|
||||
public function build()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
if(!$this->quote)
|
||||
return;
|
||||
|
||||
App::forgetInstance('translator');
|
||||
/* Init a new copy of the translator*/
|
||||
$t = app('translator');
|
||||
/* Set the locale*/
|
||||
App::setLocale($this->company->getLocale());
|
||||
/* Set customized translations _NOW_ */
|
||||
$t->replace(Ninja::transformTranslations($this->company->settings));
|
||||
|
||||
$mail_obj = new stdClass;
|
||||
$mail_obj->amount = $this->getAmount();
|
||||
$mail_obj->subject = $this->getSubject();
|
||||
$mail_obj->data = $this->getData();
|
||||
$mail_obj->markdown = 'email.admin.generic';
|
||||
$mail_obj->tag = $this->company->company_key;
|
||||
|
||||
return $mail_obj;
|
||||
}
|
||||
|
||||
private function getAmount()
|
||||
{
|
||||
return Number::formatMoney($this->quote->amount, $this->quote->client);
|
||||
}
|
||||
|
||||
private function getSubject()
|
||||
{
|
||||
return
|
||||
ctrans(
|
||||
"texts.notification_quote_approved_subject",
|
||||
[
|
||||
'client' => $this->quote->client->present()->name(),
|
||||
'invoice' => $this->quote->number,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function getData()
|
||||
{
|
||||
$settings = $this->quote->client->getMergedSettings();
|
||||
|
||||
$data = [
|
||||
'title' => $this->getSubject(),
|
||||
'message' => ctrans(
|
||||
"texts.notification_quote_approved",
|
||||
[
|
||||
'amount' => $this->getAmount(),
|
||||
'client' => $this->quote->client->present()->name(),
|
||||
'invoice' => $this->quote->number,
|
||||
]
|
||||
),
|
||||
'url' => $this->quote->invitations->first()->getAdminLink(),
|
||||
'button' => ctrans("texts.view_quote"),
|
||||
'signature' => $settings->email_signature,
|
||||
'logo' => $this->company->present()->logo(),
|
||||
'settings' => $settings,
|
||||
'whitelabel' => $this->company->account->isPaid() ? true : false,
|
||||
];
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
@ -136,4 +136,4 @@ class InvoiceEmailEngine extends BaseEmailEngine
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ use App\Models\ClientContact;
|
||||
use App\Models\User;
|
||||
use App\Services\PdfMaker\Designs\Utilities\DesignHelpers;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\TemplateEngine;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
@ -114,6 +115,10 @@ class TemplateEmail extends Mailable
|
||||
$message->invitation = $this->invitation;
|
||||
});
|
||||
|
||||
/*In the hosted platform we need to slow things down a little for Storage to catch up.*/
|
||||
if(Ninja::isHosted())
|
||||
sleep(1);
|
||||
|
||||
foreach ($this->build_email->getAttachments() as $file) {
|
||||
|
||||
if(is_string($file))
|
||||
|
@ -195,6 +195,8 @@ class BaseModel extends Model
|
||||
// Remove any runs of periods (thanks falstro!)
|
||||
$formatted_number = mb_ereg_replace("([\.]{2,})", '', $formatted_number);
|
||||
|
||||
$formatted_number = str_replace(" ", "_", $formatted_number);
|
||||
|
||||
return $formatted_number;
|
||||
}
|
||||
|
||||
|
@ -222,6 +222,9 @@ class RecurringInvoice extends BaseModel
|
||||
return null;
|
||||
}
|
||||
|
||||
nlog("frequency = $this->frequency_id");
|
||||
nlog("frequency = $this->next_send_date");
|
||||
|
||||
$offset = $this->client->timezone_offset();
|
||||
|
||||
/*
|
||||
|
@ -404,7 +404,7 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
throw new PaymentFailed($error, $e->getCode());
|
||||
}
|
||||
|
||||
public function sendFailureMail(string $error)
|
||||
public function sendFailureMail($error = '')
|
||||
{
|
||||
|
||||
if (!is_null($this->payment_hash)) {
|
||||
|
@ -86,6 +86,7 @@ class Bancontact implements MethodInterface
|
||||
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
|
||||
'metadata' => [
|
||||
'client_id' => $this->mollie->client->hashed_id,
|
||||
'hash' => $this->mollie->payment_hash->hash
|
||||
],
|
||||
]);
|
||||
|
||||
|
@ -89,6 +89,7 @@ class BankTransfer implements MethodInterface
|
||||
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
|
||||
'metadata' => [
|
||||
'client_id' => $this->mollie->client->hashed_id,
|
||||
'hash' => $this->mollie->payment_hash->hash
|
||||
],
|
||||
]);
|
||||
|
||||
|
@ -86,6 +86,7 @@ class IDEAL implements MethodInterface
|
||||
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
|
||||
'metadata' => [
|
||||
'client_id' => $this->mollie->client->hashed_id,
|
||||
'hash' => $this->mollie->payment_hash->hash
|
||||
],
|
||||
]);
|
||||
|
||||
|
@ -86,6 +86,7 @@ class KBC implements MethodInterface
|
||||
'webhookUrl' => $this->mollie->company_gateway->webhookUrl(),
|
||||
'metadata' => [
|
||||
'client_id' => $this->mollie->client->hashed_id,
|
||||
'hash' => $this->mollie->payment_hash->hash
|
||||
],
|
||||
]);
|
||||
|
||||
|
@ -93,7 +93,7 @@ class PayPalExpressPaymentDriver extends BaseDriver
|
||||
return $response->redirect();
|
||||
}
|
||||
|
||||
$this->sendFailureMail($response->getMessage());
|
||||
$this->sendFailureMail($response->getMessage() ?: '');
|
||||
|
||||
$message = [
|
||||
'server_response' => $response->getMessage(),
|
||||
@ -151,7 +151,7 @@ class PayPalExpressPaymentDriver extends BaseDriver
|
||||
|
||||
$data = $response->getData();
|
||||
|
||||
$this->sendFailureMail($response->getMessage());
|
||||
$this->sendFailureMail($response->getMessage() ?: '');
|
||||
|
||||
$message = [
|
||||
'server_response' => $data['L_LONGMESSAGE0'],
|
||||
|
@ -524,14 +524,25 @@ class StripePaymentDriver extends BaseDriver
|
||||
if ($request->type === 'charge.succeeded' || $request->type === 'payment_intent.succeeded') {
|
||||
|
||||
foreach ($request->data as $transaction) {
|
||||
$payment = Payment::query()
|
||||
->where('transaction_reference', $transaction['payment_intent'])
|
||||
|
||||
if(array_key_exists('payment_intent', $transaction))
|
||||
{
|
||||
$payment = Payment::query()
|
||||
->where('company_id', $request->getCompany()->id)
|
||||
->where(function ($query) use ($transaction) {
|
||||
$query->where('transaction_reference', $transaction['payment_intent'])
|
||||
->orWhere('transaction_reference', $transaction['id']);
|
||||
})
|
||||
->first();
|
||||
}
|
||||
else
|
||||
{
|
||||
$payment = Payment::query()
|
||||
->where('company_id', $request->getCompany()->id)
|
||||
->where('transaction_reference', $transaction['id'])
|
||||
->first();
|
||||
}
|
||||
|
||||
if ($payment) {
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
$payment->save();
|
||||
|
@ -208,8 +208,8 @@ class WePayPaymentDriver extends BaseDriver
|
||||
|
||||
return 'Processed successfully';
|
||||
} elseif ($objectType == 'account') {
|
||||
if ($accountId != $objectId) {
|
||||
throw new \Exception('Unknown account');
|
||||
if ($accountId !== $objectId) {
|
||||
throw new \Exception('Unknown account ' . $accountId . ' does not equal '.$objectId);
|
||||
}
|
||||
|
||||
$wepayAccount = $this->wepay->request('account', array(
|
||||
|
@ -171,6 +171,7 @@ use App\Listeners\Payment\PaymentEmailedActivity;
|
||||
use App\Listeners\Payment\PaymentNotification;
|
||||
use App\Listeners\Payment\PaymentRestoredActivity;
|
||||
use App\Listeners\Quote\QuoteApprovedActivity;
|
||||
use App\Listeners\Quote\QuoteApprovedNotification;
|
||||
use App\Listeners\Quote\QuoteApprovedWebhook;
|
||||
use App\Listeners\Quote\QuoteArchivedActivity;
|
||||
use App\Listeners\Quote\QuoteCreatedNotification;
|
||||
@ -437,6 +438,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
ReachWorkflowSettings::class,
|
||||
QuoteApprovedActivity::class,
|
||||
QuoteApprovedWebhook::class,
|
||||
QuoteApprovedNotification::class,
|
||||
],
|
||||
QuoteWasCreated::class => [
|
||||
CreatedQuoteActivity::class,
|
||||
|
@ -22,7 +22,9 @@ use Illuminate\Support\Str;
|
||||
*/
|
||||
class ClientContactRepository extends BaseRepository
|
||||
{
|
||||
public $is_primary;
|
||||
private bool $is_primary = true;
|
||||
|
||||
private bool $set_send_email_on_contact = false;
|
||||
|
||||
public function save(array $data, Client $client) : void
|
||||
{
|
||||
@ -36,13 +38,20 @@ class ClientContactRepository extends BaseRepository
|
||||
ClientContact::destroy($contact);
|
||||
});
|
||||
|
||||
$this->is_primary = true;
|
||||
/* Ensure send_email always exists in at least one contact */
|
||||
if(!$contacts->contains('send_email', true))
|
||||
$this->set_send_email_on_contact = true;
|
||||
|
||||
/* Set first record to primary - always */
|
||||
$contacts = $contacts->sortByDesc('is_primary')->map(function ($contact) {
|
||||
$contact['is_primary'] = $this->is_primary;
|
||||
$this->is_primary = false;
|
||||
|
||||
if($this->set_send_email_on_contact){
|
||||
$contact['send_email'] = true;
|
||||
$this->set_send_email_on_contact = false;
|
||||
}
|
||||
|
||||
return $contact;
|
||||
});
|
||||
|
||||
|
@ -129,7 +129,7 @@ class PaymentRepository extends BaseRepository {
|
||||
|
||||
//todo optimize this into a single query
|
||||
foreach ($data['invoices'] as $paid_invoice) {
|
||||
$invoice = Invoice::whereId($paid_invoice['invoice_id'])->first();
|
||||
$invoice = Invoice::withTrashed()->whereId($paid_invoice['invoice_id'])->first();
|
||||
|
||||
if ($invoice) {
|
||||
$invoice = $invoice->service()
|
||||
|
@ -14,6 +14,7 @@ namespace App\Services\Credit;
|
||||
use App\Jobs\Util\UnlinkFile;
|
||||
use App\Models\Credit;
|
||||
use App\Services\Credit\CreateInvitations;
|
||||
use App\Services\Credit\TriggeredActions;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class CreditService
|
||||
@ -150,6 +151,13 @@ class CreditService
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function triggeredActions($request)
|
||||
{
|
||||
$this->invoice = (new TriggeredActions($this->credit, $request))->run();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the credit.
|
||||
* @return Credit object
|
||||
|
76
app/Services/Credit/TriggeredActions.php
Normal file
76
app/Services/Credit/TriggeredActions.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* Credit Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Credit Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Credit;
|
||||
|
||||
use App\Events\Credit\CreditWasEmailed;
|
||||
use App\Jobs\Entity\EmailEntity;
|
||||
use App\Models\Credit;
|
||||
use App\Services\AbstractService;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TriggeredActions extends AbstractService
|
||||
{
|
||||
use GeneratesCounter;
|
||||
|
||||
private $request;
|
||||
|
||||
private $credit;
|
||||
|
||||
public function __construct(Credit $credit, Request $request)
|
||||
{
|
||||
$this->request = $request;
|
||||
|
||||
$this->credit = $credit;
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
// if ($this->request->has('auto_bill') && $this->request->input('auto_bill') == 'true') {
|
||||
// $this->credit = $this->credit->service()->autoBill()->save();
|
||||
// }
|
||||
|
||||
// if ($this->request->has('paid') && $this->request->input('paid') == 'true') {
|
||||
// $this->credit = $this->credit->service()->markPaid()->save();
|
||||
// }
|
||||
|
||||
// if ($this->request->has('amount_paid') && is_numeric($this->request->input('amount_paid')) ) {
|
||||
// $this->credit = $this->credit->service()->applyPaymentAmount($this->request->input('amount_paid'))->save();
|
||||
// }
|
||||
|
||||
if ($this->request->has('send_email') && $this->request->input('send_email') == 'true') {
|
||||
$this->sendEmail();
|
||||
}
|
||||
|
||||
if ($this->request->has('mark_sent') && $this->request->input('mark_sent') == 'true') {
|
||||
$this->credit = $this->credit->service()->markSent()->save();
|
||||
}
|
||||
|
||||
|
||||
return $this->credit;
|
||||
}
|
||||
|
||||
private function sendEmail()
|
||||
{
|
||||
|
||||
$reminder_template = $this->credit->calculateTemplate('credit');
|
||||
|
||||
$this->credit->invitations->load('contact.client.country', 'credit.client.country', 'credit.company')->each(function ($invitation) use ($reminder_template) {
|
||||
EmailEntity::dispatch($invitation, $this->credit->company, $reminder_template);
|
||||
});
|
||||
|
||||
if ($this->credit->invitations->count() > 0) {
|
||||
event(new CreditWasEmailed($this->credit->invitations->first(), $this->credit->company, Ninja::eventVars(), 'credit'));
|
||||
}
|
||||
}
|
||||
}
|
@ -47,7 +47,7 @@ class ApplyPayment extends AbstractService
|
||||
|
||||
$amount_paid = $this->payment_amount * -1;
|
||||
|
||||
$this->invoice->service()->clearPartial()->setDueDate()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($amount_paid);
|
||||
$this->invoice->service()->clearPartial()->setDueDate()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($amount_paid)->save();
|
||||
|
||||
}
|
||||
elseif ($this->invoice->partial > 0 && $this->invoice->partial > $this->payment_amount)
|
||||
@ -56,7 +56,7 @@ class ApplyPayment extends AbstractService
|
||||
|
||||
$amount_paid = $this->payment_amount * -1;
|
||||
|
||||
$this->invoice->service()->updatePartial($amount_paid)->updateBalance($amount_paid);
|
||||
$this->invoice->service()->updatePartial($amount_paid)->updateBalance($amount_paid)->save();
|
||||
|
||||
}
|
||||
elseif ($this->invoice->partial > 0 && $this->invoice->partial < $this->payment_amount)
|
||||
@ -65,7 +65,7 @@ class ApplyPayment extends AbstractService
|
||||
|
||||
$amount_paid = $this->payment_amount * -1;
|
||||
|
||||
$this->invoice->service()->clearPartial()->setDueDate()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($amount_paid);
|
||||
$this->invoice->service()->clearPartial()->setDueDate()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($amount_paid)->save();
|
||||
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ class ApplyPayment extends AbstractService
|
||||
{
|
||||
$amount_paid = $this->payment_amount * -1;
|
||||
|
||||
$this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PAID)->updateBalance($amount_paid);
|
||||
$this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PAID)->updateBalance($amount_paid)->save();
|
||||
|
||||
}
|
||||
elseif ($this->payment_amount < $this->invoice->balance)
|
||||
@ -85,7 +85,7 @@ class ApplyPayment extends AbstractService
|
||||
|
||||
$amount_paid = $this->payment_amount * -1;
|
||||
|
||||
$this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($amount_paid);
|
||||
$this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PARTIAL)->updateBalance($amount_paid)->save();
|
||||
|
||||
|
||||
}
|
||||
@ -95,7 +95,7 @@ class ApplyPayment extends AbstractService
|
||||
|
||||
$amount_paid = $this->invoice->balance * -1;
|
||||
|
||||
$this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PAID)->updateBalance($amount_paid);
|
||||
$this->invoice->service()->clearPartial()->setStatus(Invoice::STATUS_PAID)->updateBalance($amount_paid)->save();
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -82,10 +82,15 @@ class UpdateInvoicePayment
|
||||
->updateBalance($paid_amount * -1)
|
||||
->updatePaidToDate($paid_amount)
|
||||
->updateStatus()
|
||||
->deletePdf()
|
||||
->workFlow()
|
||||
->save();
|
||||
|
||||
$invoice->refresh();
|
||||
|
||||
$invoice->service()
|
||||
->deletePdf()
|
||||
->workFlow()
|
||||
->save();
|
||||
|
||||
event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
});
|
||||
|
||||
|
@ -75,9 +75,9 @@ class SubscriptionService
|
||||
$recurring_invoice = $this->convertInvoiceToRecurring($payment_hash->payment->client_id);
|
||||
$recurring_invoice_repo = new RecurringInvoiceRepository();
|
||||
|
||||
$recurring_invoice->next_send_date = now();
|
||||
$recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice);
|
||||
$recurring_invoice->next_send_date = $recurring_invoice->nextSendDate();
|
||||
// $recurring_invoice->next_send_date = now()->format('Y-m-d');
|
||||
// $recurring_invoice->next_send_date = $recurring_invoice->nextSendDate();
|
||||
$recurring_invoice->auto_bill = $this->subscription->auto_bill;
|
||||
|
||||
/* Start the recurring service */
|
||||
@ -161,6 +161,11 @@ class SubscriptionService
|
||||
$recurring_invoice->discount = $this->subscription->promo_discount;
|
||||
$recurring_invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||
}
|
||||
elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0) {
|
||||
$recurring_invoice->discount = $this->subscription->promo_discount;
|
||||
$recurring_invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||
}
|
||||
|
||||
|
||||
$recurring_invoice = $recurring_invoice_repo->save($data, $recurring_invoice);
|
||||
|
||||
@ -594,7 +599,7 @@ class SubscriptionService
|
||||
|
||||
$recurring_invoice = $this->convertInvoiceToRecurring($old_recurring_invoice->client_id);
|
||||
$recurring_invoice = $recurring_invoice_repo->save([], $recurring_invoice);
|
||||
$recurring_invoice->next_send_date = now();
|
||||
$recurring_invoice->next_send_date = now()->format('Y-m-d');
|
||||
$recurring_invoice->next_send_date = $recurring_invoice->nextSendDate();
|
||||
|
||||
/* Start the recurring service */
|
||||
@ -693,6 +698,11 @@ class SubscriptionService
|
||||
$invoice->discount = $this->subscription->promo_discount;
|
||||
$invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||
}
|
||||
elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0) {
|
||||
$invoice->discount = $this->subscription->promo_discount;
|
||||
$invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||
}
|
||||
|
||||
|
||||
return $invoice_repo->save($data, $invoice);
|
||||
|
||||
@ -722,7 +732,9 @@ class SubscriptionService
|
||||
$recurring_invoice->auto_bill = $client->getSetting('auto_bill');
|
||||
$recurring_invoice->auto_bill_enabled = $this->setAutoBillFlag($recurring_invoice->auto_bill);
|
||||
$recurring_invoice->due_date_days = 'terms';
|
||||
|
||||
$recurring_invoice->next_send_date = now()->format('Y-m-d');
|
||||
$recurring_invoice->next_send_date = $recurring_invoice->nextSendDate();
|
||||
|
||||
return $recurring_invoice;
|
||||
}
|
||||
|
||||
@ -1027,7 +1039,7 @@ class SubscriptionService
|
||||
'subscription' => $this->subscription->hashed_id,
|
||||
'recurring_invoice' => $recurring_invoice_hashed_id,
|
||||
'client' => $invoice->client->hashed_id,
|
||||
'contact' => $invoice->client->primary_contact()->first() ? $invoice->client->primary_contact()->first(): $invoice->client->contacts->first(),
|
||||
'contact' => $invoice->client->primary_contact()->first() ? $invoice->client->primary_contact()->first()->hashed_id: $invoice->client->contacts->first()->hashed_id,
|
||||
'invoice' => $invoice->hashed_id,
|
||||
];
|
||||
|
||||
|
@ -150,4 +150,4 @@
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
||||
}
|
@ -14,8 +14,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => '5.3.31',
|
||||
'app_tag' => '5.3.31',
|
||||
'app_version' => '5.3.32',
|
||||
'app_tag' => '5.3.32',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
@ -25,8 +25,8 @@
|
||||
@foreach($multiple_contacts as $contact)
|
||||
<a data-turbolinks="false"
|
||||
href="{{ route('client.switch_company', $contact->hashed_id) }}"
|
||||
class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ $contact->company->present()->name() }}
|
||||
- {{ $contact->client->present()->name()}}</a>
|
||||
class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">{{ $contact->client->present()->name()}} - {{ $contact->company->present()->name() }}
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
@ -23,7 +23,7 @@ Route::get('tmp_pdf/{hash}', 'ClientPortal\TempRouteController@index')->name('tm
|
||||
|
||||
Route::get('client/key_login/{contact_key}', 'ClientPortal\ContactHashLoginController@login')->name('client.contact_login')->middleware(['domain_db','contact_key_login']);
|
||||
Route::get('client/magic_link/{magic_link}', 'ClientPortal\ContactHashLoginController@magicLink')->name('client.contact_magic_link')->middleware(['domain_db','contact_key_login']);
|
||||
Route::get('documents/{document_hash}', 'ClientPortal\DocumentController@publicDownload')->name('documents.public_download')->middleware(['document_db']);
|
||||
Route::get('documents/{document_hash}', 'ClientPortal\DocumentController@publicDownload')->name('documents.public_download')->middleware(['domain_db']);
|
||||
Route::get('error', 'ClientPortal\ContactHashLoginController@errorPage')->name('client.error');
|
||||
Route::get('client/payment/{contact_key}/{payment_id}', 'ClientPortal\InvitationController@paymentRouter')->middleware(['domain_db','contact_key_login']);
|
||||
Route::get('client/ninja/{contact_key}/{company_key}', 'ClientPortal\NinjaPlanController@index')->name('client.ninja_contact_login')->middleware(['domain_db']);
|
||||
|
@ -10,6 +10,7 @@
|
||||
*/
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Factory\ClientContactFactory;
|
||||
use App\Factory\InvoiceItemFactory;
|
||||
use App\Utils\Traits\UserSessionAttributes;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
@ -77,4 +78,41 @@ class CollectionMergingTest extends TestCase
|
||||
$this->assertTrue(collect($items)->contains('type_id', 3));
|
||||
|
||||
}
|
||||
|
||||
public function testClientContactSendEmailExists()
|
||||
{
|
||||
$new_collection = collect();
|
||||
|
||||
$cc = ClientContactFactory::create(1,1);
|
||||
$cc->send_email = true;
|
||||
|
||||
$new_collection->push($cc);
|
||||
|
||||
$cc_false = ClientContactFactory::create(1,1);
|
||||
$cc_false->send_email = false;
|
||||
|
||||
$new_collection->push($cc_false);
|
||||
|
||||
$this->assertTrue($new_collection->contains('send_email', true));
|
||||
|
||||
}
|
||||
|
||||
public function testClientContactSendEmailDoesNotExists()
|
||||
{
|
||||
$new_collection = collect();
|
||||
|
||||
$cc = ClientContactFactory::create(1,1);
|
||||
$cc->send_email = false;
|
||||
|
||||
$new_collection->push($cc);
|
||||
|
||||
$cc_false = ClientContactFactory::create(1,1);
|
||||
$cc_false->send_email = false;
|
||||
|
||||
$new_collection->push($cc_false);
|
||||
|
||||
$this->assertFalse($new_collection->contains('send_email', true));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user