Merge branch 'v5-develop' into yodlee

This commit is contained in:
David Bomba 2022-10-06 20:55:44 +11:00
commit 0a2cb6f88d
174 changed files with 505620 additions and 633525 deletions

View File

@ -1 +1 @@
5.5.19
5.5.27

View File

@ -20,6 +20,7 @@ use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Company;
use App\Models\CompanyLedger;
use App\Models\CompanyUser;
use App\Models\Contact;
use App\Models\Credit;
use App\Models\CreditInvitation;
@ -702,13 +703,23 @@ class CheckData extends Command
->count();
if($count == 0){
$this->logMessage("# {$client->id} # {$client->name} {$client->balance} is invalid should be 0");
//factor in over payments to the client balance
$over_payment = Payment::where('client_id', $client->id)
->where('is_deleted', 0)
->whereIn('status_id', [1,4])
->selectRaw('sum(amount - applied) as p')
->pluck('p')
->first();
if($this->option('client_balance')){
$this->logMessage("# {$client->id} # {$client->name} {$client->balance} is invalid should be {$over_payment}");
if($this->option('client_balance') && (floatval($over_payment) != floatval($client->balance) )){
$this->logMessage("# {$client->id} " . $client->present()->name().' - '.$client->number." Fixing {$client->balance} to 0");
$client->balance = 0;
$client->balance = $over_payment * -1;
$client->save();
}

View File

@ -0,0 +1,59 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\DataMapper\Analytics;
use Turbo124\Beacon\ExampleMetric\GenericMixedMetric;
class EmailCount extends GenericMixedMetric
{
/**
* The type of Sample.
*
* Monotonically incrementing counter
*
* - counter
*
* @var string
*/
public $type = 'mixed_metric';
/**
* The name of the counter.
* @var string
*/
public $name = 'account.daily_email_count';
/**
* The datetime of the counter measurement.
*
* date("Y-m-d H:i:s")
*
* @var DateTime
*/
public $datetime;
/**
* The Class failure name
* set to 0.
*
* @var string
*/
public $string_metric5 = 'account_key';
public $int_metric1 = 1;
public function __construct($int_metric1, $string_metric5)
{
$this->int_metric1 = $int_metric1;
$this->string_metric5 = $string_metric5;
}
}

View File

@ -26,6 +26,7 @@ class CompanySettings extends BaseSettings
public $auto_archive_invoice = false; // @implemented
public $qr_iban = ''; //@implemented
public $besr_id = ''; //@implemented
public $lock_invoices = 'off'; //off,when_sent,when_paid //@implemented

View File

@ -44,6 +44,10 @@ class RecurringExpenseToExpenseFactory
$expense->payment_date = $recurring_expense->payment_date;
$expense->amount = $recurring_expense->amount;
$expense->foreign_amount = $recurring_expense->foreign_amount ?: 0;
//11-09-2022 - we should be tracking the recurring expense!!
$expense->recurring_expense_id = $recurring_expense->id;
// $expense->private_notes = $recurring_expense->private_notes;
// $expense->public_notes = $recurring_expense->public_notes;

View File

@ -29,10 +29,7 @@ class RecurringInvoiceToInvoiceFactory
$invoice->terms = self::tranformObject($recurring_invoice->terms, $client);
$invoice->public_notes = self::tranformObject($recurring_invoice->public_notes, $client);
$invoice->private_notes = $recurring_invoice->private_notes;
//$invoice->date = now()->format($client->date_format());
//$invoice->due_date = $recurring_invoice->calculateDueDate(now());
$invoice->is_deleted = $recurring_invoice->is_deleted;
// $invoice->line_items = $recurring_invoice->line_items;
$invoice->line_items = self::transformItems($recurring_invoice, $client);
$invoice->tax_name1 = $recurring_invoice->tax_name1;
$invoice->tax_rate1 = $recurring_invoice->tax_rate1;

View File

@ -30,7 +30,10 @@ class ClientFilters extends QueryFilters
*/
public function name(string $name): Builder
{
return $this->builder->where('name', 'like', '%'.$name.'%');
if(strlen($name) >=1)
return $this->builder->where('name', 'like', '%'.$name.'%');
return $this->builder;
}
/**

View File

@ -90,7 +90,7 @@ class SwissQrGenerator
$this->client->address1 ? substr($this->client->address1, 0 , 70) : '',
$this->client->address2 ? substr($this->client->address2, 0 , 16) : '',
$this->client->postal_code ? substr($this->client->postal_code, 0, 16) : '',
$this->client->city ? substr($this->client->postal_code, 0, 35) : '',
$this->client->city ? substr($this->client->city, 0, 35) : '',
'CH'
));
@ -104,16 +104,39 @@ class SwissQrGenerator
// Add payment reference
// This is what you will need to identify incoming payments.
$referenceNumber = QrBill\Reference\QrPaymentReferenceGenerator::generate(
$this->company->present()->besr_id() ?: '', // You receive this number from your bank (BESR-ID). Unless your bank is PostFinance, in that case use NULL.
$this->invoice->number// A number to match the payment with your internal data, e.g. an invoice number
);
$qrBill->setPaymentReference(
QrBill\DataGroup\Element\PaymentReference::create(
QrBill\DataGroup\Element\PaymentReference::TYPE_QR,
$referenceNumber
));
if(stripos($this->invoice->number, "Live-") === 0)
{
// we're currently in preview status. Let's give a dummy reference for now
$invoice_number = "123456789";
}
else
{
$invoice_number = $this->invoice->number;
}
if(strlen($this->company->present()->besr_id()) > 1)
{
$referenceNumber = QrBill\Reference\QrPaymentReferenceGenerator::generate(
$this->company->present()->besr_id() ?: '', // You receive this number from your bank (BESR-ID). Unless your bank is PostFinance, in that case use NULL.
$invoice_number// A number to match the payment with your internal data, e.g. an invoice number
);
$qrBill->setPaymentReference(
QrBill\DataGroup\Element\PaymentReference::create(
QrBill\DataGroup\Element\PaymentReference::TYPE_QR,
$referenceNumber
));
}
else{
$qrBill->setPaymentReference(
QrBill\DataGroup\Element\PaymentReference::create(
QrBill\DataGroup\Element\PaymentReference::TYPE_NON
));
}
// Optionally, add some human-readable information about what the bill is for.
$qrBill->setAdditionalInformation(
@ -141,6 +164,8 @@ class SwissQrGenerator
nlog($violation);
}
nlog($e->getMessage());
return '';
// return $e->getMessage();
}
@ -148,4 +173,4 @@ class SwissQrGenerator
}
}
}

View File

@ -41,7 +41,7 @@ class ActivityController extends BaseController
/**
* @OA\Get(
* path="/api/v1/actvities",
* path="/api/v1/activities",
* operationId="getActivities",
* tags={"actvities"},
* summary="Gets a list of actvities",

View File

@ -810,8 +810,16 @@ class BaseController extends Controller
// 10-01-2022 need to ensure we snake case properly here to ensure permissions work as expected
// 28-03-2022 this is definitely correct here, do not append _ to the view, it resolved correctly when snake cased
if (auth()->user() && ! auth()->user()->hasPermission('view'.lcfirst(class_basename(Str::snake($this->entity_type))))) {
//03-09-2022
$query->where('user_id', '=', auth()->user()->id)->orWhere('assigned_user_id', auth()->user()->id);
//06-10-2022 - some entities do not have assigned_user_id - this becomes an issue when we have a large company and low permission users
if(lcfirst(class_basename(Str::snake($this->entity_type))) == 'user')
$query->where('id', auth()->user()->id);
elseif(in_array(lcfirst(class_basename(Str::snake($this->entity_type))),['design','group_setting','payment_term'])){
//need to pass these back regardless
}
else
$query->where('user_id', '=', auth()->user()->id)->orWhere('assigned_user_id', auth()->user()->id);
}
if (request()->has('updated_at') && request()->input('updated_at') > 0) {

View File

@ -56,7 +56,7 @@ class InvoiceController extends Controller
{
set_time_limit(0);
$invoice->service()->removeUnpaidGatewayFees()->save();
// $invoice->service()->removeUnpaidGatewayFees()->save();
$invitation = $invoice->invitations()->where('client_contact_id', auth()->guard('contact')->user()->id)->first();

View File

@ -179,7 +179,7 @@ class NinjaPlanController extends Controller
->queue();
$ninja_company = Company::on('db-ninja-01')->find(config('ninja.ninja_default_company_id'));
$ninja_company->notification(new NewAccountNotification($account, $client))->ninja();
$ninja_company->notification(new NewAccountNotification($subscription->company->account, $client))->ninja();
return $this->render('plan.trial_confirmed', $data);
}

View File

@ -131,8 +131,10 @@ class EmailController extends BaseController
if(Ninja::isHosted() && !$entity_obj->company->account->account_sms_verified)
return response(['message' => 'Please verify your account to send emails.'], 400);
if($entity == 'purchaseOrder' || $entity == 'purchase_order' || $template == 'purchase_order'){
return $this->sendPurchaseOrder($entity_obj, $data);
nlog($entity);
if($entity == 'purchaseOrder' || $entity == 'purchase_order' || $template == 'purchase_order' || $entity == 'App\Models\PurchaseOrder'){
return $this->sendPurchaseOrder($entity_obj, $data, $template);
}
$entity_obj->invitations->each(function ($invitation) use ($data, $entity_string, $entity_obj, $template) {
@ -183,13 +185,15 @@ class EmailController extends BaseController
return $this->itemResponse($entity_obj->fresh());
}
private function sendPurchaseOrder($entity_obj, $data)
private function sendPurchaseOrder($entity_obj, $data, $template)
{
$this->entity_type = PurchaseOrder::class;
$this->entity_transformer = PurchaseOrderTransformer::class;
$data['template'] = $template;
PurchaseOrderEmail::dispatch($entity_obj, $entity_obj->company, $data);
return $this->itemResponse($entity_obj);

View File

@ -578,6 +578,16 @@ class InvoiceController extends BaseController
return response()->json(['message' => ctrans('texts.sent_message')], 200);
}
if($action == 'download' && $invoices->count() >=1 && auth()->user()->can('view', $invoices->first())) {
$file = $invoices->first()->service()->getInvoicePdf();
return response()->streamDownload(function () use ($file) {
echo Storage::get($file);
}, basename($file), ['Content-Type' => 'application/pdf']);
}
/*
* Send the other actions to the switch
*/

View File

@ -306,7 +306,7 @@ class PreviewController extends BaseController
if (Ninja::isHosted()) {
LightLogs::create(new LivePreview())
->increment()
->queue();
->batch();
}
$response = Response::make($file_path, 200);

View File

@ -292,7 +292,7 @@ class PreviewPurchaseOrderController extends BaseController
{
LightLogs::create(new LivePreview())
->increment()
->queue();
->batch();
}

View File

@ -210,7 +210,7 @@ class RecurringInvoiceController extends BaseController
event(new RecurringInvoiceWasCreated($recurring_invoice, $recurring_invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
return $this->itemResponse($recurring_invoice);
return $this->itemResponse($recurring_invoice->fresh());
}
/**

View File

@ -69,39 +69,7 @@ class SelfUpdateController extends BaseController
* ),
* )
*/
// public function old_update(\Codedge\Updater\UpdaterManager $updater)
// {
// set_time_limit(0);
// define('STDIN', fopen('php://stdin', 'r'));
// if (Ninja::isHosted()) {
// return response()->json(['message' => ctrans('texts.self_update_not_available')], 403);
// }
// $this->testWritable();
// // Get the new version available
// $versionAvailable = $updater->source()->getVersionAvailable();
// // Create a release
// $release = $updater->source()->fetch($versionAvailable);
// $updater->source()->update($release);
// $cacheCompiled = base_path('bootstrap/cache/compiled.php');
// if (file_exists($cacheCompiled)) { unlink ($cacheCompiled); }
// $cacheServices = base_path('bootstrap/cache/services.php');
// if (file_exists($cacheServices)) { unlink ($cacheServices); }
// Artisan::call('clear-compiled');
// Artisan::call('route:clear');
// Artisan::call('view:clear');
// Artisan::call('optimize');
// return response()->json(['message' => 'Update completed'], 200);
// }
public function update()
{
set_time_limit(0);

View File

@ -40,7 +40,10 @@ class WePayController extends BaseController
$company = Company::where('company_key', $hash['company_key'])->firstOrFail();
$data['user_id'] = $user->id;
$data['company'] = $company;
$data['user_company'] = $company;
// $data['company_key'] = $company->company_key;
// $data['db'] = $company->db;
$wepay_driver = new WePayPaymentDriver(new CompanyGateway, null, null);

View File

@ -98,8 +98,6 @@ class Kernel extends HttpKernel
],
'api' => [
// 'throttle:300,1',
// 'cors',
'bindings',
'query_logging',
],

View File

@ -35,7 +35,7 @@ class CreditsTable extends Component
public function render()
{
$query = Credit::query()
->where('client_id', auth()->guard('contact')->user()->client->id)
->where('client_id', auth()->guard('contact')->user()->client_id)
->where('company_id', $this->company->id)
->where('status_id', '<>', Credit::STATUS_DRAFT)
->where('is_deleted', 0)

View File

@ -43,10 +43,10 @@ class InvoicesTable extends Component
$local_status = [];
$query = Invoice::query()
->with('client.gateway_tokens', 'client.contacts')
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
->where('company_id', $this->company->id)
->where('is_deleted', false);
->where('is_deleted', false)
->with('client.gateway_tokens', 'client.contacts')
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc');
if (in_array('paid', $this->status)) {
$local_status[] = Invoice::STATUS_PAID;

View File

@ -40,9 +40,9 @@ class PaymentsTable extends Component
{
$query = Payment::query()
->with('type', 'client')
->whereIn('status_id', [Payment::STATUS_FAILED, Payment::STATUS_COMPLETED, Payment::STATUS_PENDING, Payment::STATUS_REFUNDED, Payment::STATUS_PARTIALLY_REFUNDED])
->where('company_id', $this->company->id)
->where('client_id', auth()->guard('contact')->user()->client->id)
->where('client_id', auth()->guard('contact')->user()->client_id)
->whereIn('status_id', [Payment::STATUS_FAILED, Payment::STATUS_COMPLETED, Payment::STATUS_PENDING, Payment::STATUS_REFUNDED, Payment::STATUS_PARTIALLY_REFUNDED])
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
->withTrashed()
->paginate($this->per_page);

View File

@ -45,10 +45,10 @@ class PurchaseOrdersTable extends Component
$query = PurchaseOrder::query()
->with('vendor.contacts')
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc')
->whereIn('status_id', [PurchaseOrder::STATUS_SENT, PurchaseOrder::STATUS_ACCEPTED])
->where('company_id', $this->company->id)
->where('is_deleted', false);
->whereIn('status_id', [PurchaseOrder::STATUS_SENT, PurchaseOrder::STATUS_ACCEPTED])
->where('is_deleted', false)
->orderBy($this->sort_field, $this->sort_asc ? 'asc' : 'desc');
if (in_array('sent', $this->status)) {
$local_status[] = PurchaseOrder::STATUS_SENT;

View File

@ -79,9 +79,9 @@ class QuotesTable extends Component
$query = $query
->where('company_id', $this->company->id)
->where('client_id', auth()->guard('contact')->user()->client->id)
->where('status_id', '<>', Quote::STATUS_DRAFT)
->where('client_id', auth()->guard('contact')->user()->client_id)
->where('is_deleted', 0)
->where('status_id', '<>', Quote::STATUS_DRAFT)
->withTrashed()
->paginate($this->per_page);

View File

@ -40,7 +40,7 @@ class RecurringInvoicesTable extends Component
$query = RecurringInvoice::query();
$query = $query
->where('client_id', auth()->guard('contact')->user()->client->id)
->where('client_id', auth()->guard('contact')->user()->client_id)
->where('company_id', $this->company->id)
->whereIn('status_id', [RecurringInvoice::STATUS_ACTIVE])
->orderBy('status_id', 'asc')

View File

@ -14,6 +14,7 @@ namespace App\Http\Livewire;
use App\Libraries\MultiDB;
use App\Models\ClientContact;
use App\Models\CompanyGateway;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Livewire\Component;
@ -105,6 +106,8 @@ class RequiredClientInfo extends Component
public $company;
public $company_gateway_id;
public function mount()
{
MultiDB::setDb($this->company->db);
@ -141,6 +144,8 @@ class RequiredClientInfo extends Component
'client_postal_code' => $this->contact->client->postal_code,
]);
//if stripe is enabled, we want to update the customer at this point.
return true;
}
@ -150,6 +155,7 @@ class RequiredClientInfo extends Component
private function updateClientDetails(array $data): bool
{
nlog($this->company->id);
$client = [];
$contact = [];
@ -172,6 +178,16 @@ class RequiredClientInfo extends Component
->push();
if ($contact_update && $client_update) {
$cg = CompanyGateway::find($this->company_gateway_id);
if($cg && $cg->update_details){
$payment_gateway = $cg->driver($this->client)->init();
if(method_exists($payment_gateway, "updateCustomer"))
$payment_gateway->updateCustomer();
}
return true;
}

View File

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

View File

@ -69,8 +69,8 @@ class QueryLogging
$ip = request()->ip();
}
LightLogs::create(new DbQuery($request->method(), urldecode($request->url()), $count, $time, $ip))
->queue();
LightLogs::create(new DbQuery($request->method(), substr(urldecode($request->url()),0,180), $count, $time, $ip))
->batch();
}
return $response;

View File

@ -158,6 +158,10 @@ class StoreClientRequest extends Request
unset($input['number']);
}
if (array_key_exists('name', $input)) {
$input['name'] = strip_tags($input['name']);
}
$this->replace($input);
}

View File

@ -112,6 +112,10 @@ class UpdateClientRequest extends Request
$input['settings'] = $this->filterSaveableSettings($input['settings']);
}
if (array_key_exists('name', $input)) {
$input['name'] = strip_tags($input['name']);
}
$this->replace($input);
}

View File

@ -65,15 +65,15 @@ class StoreCompanyRequest extends Request
$input['google_analytics_key'] = $input['google_analytics_url'];
}
$company_settings = CompanySettings::defaults();
// $company_settings = CompanySettings::defaults();
//@todo this code doesn't make sense as we never return $company_settings anywhere
//@deprecated???
if (array_key_exists('settings', $input) && ! empty($input['settings'])) {
foreach ($input['settings'] as $key => $value) {
$company_settings->{$key} = $value;
}
}
// if (array_key_exists('settings', $input) && ! empty($input['settings'])) {
// foreach ($input['settings'] as $key => $value) {
// $company_settings->{$key} = $value;
// }
// }
if (array_key_exists('portal_domain', $input)) {
$input['portal_domain'] = strtolower($input['portal_domain']);

View File

@ -49,7 +49,7 @@ class StoreGroupSettingRequest extends Request
}
}
$input['settings'] = $group_settings;
$input['settings'] = (array)$group_settings;
$this->replace($input);
}

View File

@ -73,6 +73,6 @@ class UpdateGroupSettingRequest extends Request
}
}
return $settings;
return (array)$settings;
}
}

View File

@ -62,7 +62,7 @@ class StoreInvoiceRequest extends Request
$rules['invitations.*.client_contact_id'] = 'distinct';
$rules['number'] = ['nullable', Rule::unique('invoices')->where('company_id', auth()->user()->company()->id)];
$rules['number'] = ['bail', 'nullable', Rule::unique('invoices')->where('company_id', auth()->user()->company()->id)];
$rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())];
$rules['is_amount_discount'] = ['boolean'];
@ -95,6 +95,9 @@ class StoreInvoiceRequest extends Request
if (array_key_exists('tax_rate3', $input) && is_null($input['tax_rate3'])) {
$input['tax_rate3'] = 0;
}
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {
$input['exchange_rate'] = 1;
}
$this->replace($input);
}

View File

@ -109,6 +109,7 @@ class StorePaymentRequest extends Request
'credits.*.amount' => ['bail','required', new CreditsSumRule($this->all())],
'invoices' => new ValidPayableInvoicesRule(),
'number' => ['nullable', 'bail', Rule::unique('payments')->where('company_id', auth()->user()->company()->id)],
'idempotency_key' => ['nullable', 'bail', 'string','max:64', Rule::unique('payments')->where('company_id', auth()->user()->company()->id)],
];

View File

@ -85,6 +85,14 @@ class StoreUserRequest extends Request
];
}
if (array_key_exists('first_name', $input)) {
$input['first_name'] = strip_tags($input['first_name']);
}
if (array_key_exists('last_name', $input)) {
$input['last_name'] = strip_tags($input['last_name']);
}
$this->replace($input);
}

View File

@ -49,6 +49,14 @@ class UpdateUserRequest extends Request
$input['email'] = trim($input['email']);
}
if (array_key_exists('first_name', $input)) {
$input['first_name'] = strip_tags($input['first_name']);
}
if (array_key_exists('last_name', $input)) {
$input['last_name'] = strip_tags($input['last_name']);
}
$this->replace($input);
}
}

View File

@ -26,6 +26,7 @@ class BlackListRule implements Rule
'arxxwalls.com',
'superhostforumla.com',
'wnpop.com',
'dataservices.space',
];
/**

View File

@ -28,6 +28,15 @@ class ExpenseMap
9 => 'expense.transaction_reference',
10 => 'expense.public_notes',
11 => 'expense.private_notes',
12 => 'expense.tax_name1',
13 => 'expense.tax_rate1',
14 => 'expense.tax_name2',
15 => 'expense.tax_rate2',
16 => 'expense.tax_name3',
17 => 'expense.tax_rate3',
18 => 'expense.uses_inclusive_taxes',
19 => 'expense.payment_date',
];
}
@ -46,6 +55,15 @@ class ExpenseMap
9 => 'texts.transaction_reference',
10 => 'texts.public_notes',
11 => 'texts.private_notes',
12 => 'texts.tax_name1',
13 => 'texts.tax_rate1',
14 => 'texts.tax_name2',
15 => 'texts.tax_rate2',
16 => 'texts.tax_name3',
17 => 'texts.tax_rate3',
18 => 'texts.uses_inclusive_taxes',
19 => 'texts.payment_date',
];
}
}

View File

@ -173,18 +173,18 @@ class BaseImport
$is_free_hosted_client = $this->company->account->isFreeHostedClient();
$hosted_client_count = $this->company->account->hosted_client_count;
if($this->factory_name == 'App\Factory\ClientFactory' && $is_free_hosted_client && (count($data) > $hosted_client_count))
{
$this->error_array[$entity_type][] = [
$entity_type => 'client',
'error' => 'Error, you are attempting to import more clients than your plan allows',
];
return $count;
}
foreach ($data as $key => $record) {
if($this->factory_name instanceof ClientFactory && $is_free_hosted_client && ($this->company->clients()->count() > $hosted_client_count))
{
$this->error_array[$entity_type][] = [
$entity_type => $record,
'error' => 'Client limit reached',
];
return $count;
}
try {
$entity = $this->transformer->transform($record);
// $validator = $this->request_name::runFormRequest($entity);

View File

@ -178,12 +178,14 @@ class BaseTransformer
public function getFloat($data, $field)
{
if (array_key_exists($field, $data)) {
$number = preg_replace('/[^0-9-.]+/', '', $data[$field]);
//$number = preg_replace('/[^0-9-.]+/', '', $data[$field]);
return Number::parseStringFloat($data[$field]);
} else {
$number = 0;
//$number = 0;
return 0;
}
return Number::parseFloat($number);
// return Number::parseFloat($number);
}
/**

View File

@ -42,7 +42,7 @@ class ExpenseTransformer extends BaseTransformer
'client_id' => isset($data['expense.client'])
? $this->getClientId($data['expense.client'])
: null,
'date' => strlen($this->getString($data, 'expense.date') > 1) ? date('Y-m-d', strtotime($this->getString($data, 'expense.date'))) : now()->format('Y-m-d'),
'date' => strlen($this->getString($data, 'expense.date') > 1) ? date('Y-m-d', strtotime(str_replace("/","-",$data['expense.date']))) : now()->format('Y-m-d'),
'public_notes' => $this->getString($data, 'expense.public_notes'),
'private_notes' => $this->getString($data, 'expense.private_notes'),
'category_id' => isset($data['expense.category'])
@ -55,7 +55,7 @@ class ExpenseTransformer extends BaseTransformer
? $this->getPaymentTypeId($data['expense.payment_type'])
: null,
'payment_date' => isset($data['expense.payment_date'])
? date('Y-m-d', strtotime($data['expense.payment_date']))
? date('Y-m-d', strtotime(str_replace("/","-",$data['expense.payment_date'])))
: null,
'custom_value1' => $this->getString($data, 'expense.custom_value1'),
'custom_value2' => $this->getString($data, 'expense.custom_value2'),
@ -66,6 +66,14 @@ class ExpenseTransformer extends BaseTransformer
'expense.transaction_reference'
),
'should_be_invoiced' => $clientId ? true : false,
'uses_inclusive_taxes' => (bool) $this->getString($data, 'expense.uses_inclusive_taxes'),
'tax_name1' => $this->getString($data, 'expense.tax_name1'),
'tax_rate1' => $this->getFloat($data, 'expense.tax_rate1'),
'tax_name2' => $this->getString($data, 'expense.tax_name2'),
'tax_rate2' => $this->getFloat($data, 'expense.tax_rate2'),
'tax_name3' => $this->getString($data, 'expense.tax_name3'),
'tax_rate3' => $this->getFloat($data, 'expense.tax_rate3'),
];
}
}

View File

@ -57,10 +57,10 @@ class InvoiceTransformer extends BaseTransformer
'discount' => $this->getFloat($invoice_data, 'invoice.discount'),
'po_number' => $this->getString($invoice_data, 'invoice.po_number'),
'date' => isset($invoice_data['invoice.date'])
? date('Y-m-d', strtotime($invoice_data['invoice.date']))
? date('Y-m-d', strtotime(str_replace("/","-",$invoice_data['invoice.date'])))
: now()->format('Y-m-d'),
'due_date' => isset($invoice_data['invoice.due_date'])
? date('Y-m-d', strtotime($invoice_data['invoice.due_date']))
? date('Y-m-d', strtotime(str_replace("/","-",$invoice_data['invoice.due_date'])))
: null,
'terms' => $this->getString($invoice_data, 'invoice.terms'),
'public_notes' => $this->getString(

View File

@ -57,10 +57,10 @@ class QuoteTransformer extends BaseTransformer
'discount' => $this->getFloat($quote_data, 'quote.discount'),
'po_number' => $this->getString($quote_data, 'quote.po_number'),
'date' => isset($quote_data['quote.date'])
? date('Y-m-d', strtotime($quote_data['quote.date']))
? date('Y-m-d', strtotime(str_replace("/","-",$quote_data['quote.date'])))
: now()->format('Y-m-d'),
'due_date' => isset($quote_data['quote.due_date'])
? date('Y-m-d', strtotime($quote_data['quote.due_date']))
? date('Y-m-d', strtotime(str_replace("/","-",$quote_data['quote.due_date'])))
: null,
'terms' => $this->getString($quote_data, 'quote.terms'),
'public_notes' => $this->getString(

View File

@ -86,7 +86,7 @@ class CreateAccount
$sp794f3f->hosted_company_count = config('ninja.quotas.free.max_companies');
$sp794f3f->account_sms_verified = true;
if(in_array($this->getDomain($this->request['email']), ['gmail.com', 'hotmail.com', 'outlook.com', 'yahoo.com'])){
if(in_array($this->getDomain($this->request['email']), ['gmail.com', 'hotmail.com', 'outlook.com', 'yahoo.com', 'aol.com', 'mail.ru'])){
$sp794f3f->account_sms_verified = false;
}

View File

@ -13,6 +13,7 @@ namespace App\Jobs\Cron;
use App\Jobs\RecurringInvoice\SendRecurring;
use App\Libraries\MultiDB;
use App\Models\Invoice;
use App\Models\RecurringInvoice;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Support\Carbon;
@ -107,7 +108,7 @@ class RecurringInvoicesCron
nlog("Trying to send {$recurring_invoice->number}");
if ($recurring_invoice->company->stop_on_unpaid_recurring) {
if ($recurring_invoice->invoices()->whereIn('status_id', [2, 3])->where('is_deleted', 0)->where('balance', '>', 0)->exists()) {
if (Invoice::where('recurring_id', $recurring_invoice->id)->whereIn('status_id', [2, 3])->where('is_deleted', 0)->where('balance', '>', 0)->exists()) {
return;
}
}

View File

@ -23,6 +23,7 @@ use App\Models\Activity;
use App\Models\Company;
use App\Models\CreditInvitation;
use App\Models\InvoiceInvitation;
use App\Models\PurchaseOrderInvitation;
use App\Models\QuoteInvitation;
use App\Models\RecurringInvoiceInvitation;
use App\Utils\HtmlEngine;
@ -77,12 +78,12 @@ class EmailEntity implements ShouldQueue
$this->invitation = $invitation;
$this->settings = $invitation->contact->client->getMergedSettings();
$this->entity_string = $this->resolveEntityString();
$this->entity = $invitation->{$this->entity_string};
$this->settings = $invitation->contact->client->getMergedSettings();
$this->reminder_template = $reminder_template ?: $this->entity->calculateTemplate($this->entity_string);
$this->html_engine = new HtmlEngine($invitation);

View File

@ -37,7 +37,7 @@ class AdjustProductInventory implements ShouldQueue
public array $old_invoice;
public function __construct(Company $company, Invoice $invoice, ?array $old_invoice = [])
public function __construct(Company $company, Invoice $invoice, $old_invoice = [])
{
$this->company = $company;
$this->invoice = $invoice;

View File

@ -51,7 +51,7 @@ class NinjaMailerJob implements ShouldQueue
public $tries = 3; //number of retries
public $backoff = 10; //seconds to wait until retry
public $backoff = 30; //seconds to wait until retry
public $deleteWhenMissingModels = true;
@ -123,7 +123,7 @@ class NinjaMailerJob implements ShouldQueue
->send($this->nmo->mailable);
LightLogs::create(new EmailSuccess($this->nmo->company->company_key))
->queue();
->batch();
/* Count the amount of emails sent across all the users accounts */
Cache::increment($this->company->account->key);

View File

@ -11,6 +11,7 @@
namespace App\Jobs\Ninja;
use App\DataMapper\Analytics\EmailCount;
use App\Libraries\MultiDB;
use App\Models\Account;
use App\Utils\Ninja;
@ -20,6 +21,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Cache;
use Turbo124\Beacon\Facades\LightLogs;
class AdjustEmailQuota implements ShouldQueue
{
@ -58,8 +60,15 @@ class AdjustEmailQuota implements ShouldQueue
{
Account::query()->cursor()->each(function ($account) {
nlog("resetting email quota for {$account->key}");
$email_count = Cache::get($account->key);
if($email_count > 0)
LightLogs::create(new EmailCount($email_count, $account->key))->batch();
Cache::forget($account->key);
Cache::forget("throttle_notified:{$account->key}");
});
}
}

View File

@ -217,7 +217,7 @@ class ProcessPostmarkWebhook implements ShouldQueue
$this->request['MessageID']
);
LightLogs::create($bounce)->queue();
LightLogs::create($bounce)->batch();
SystemLogger::dispatch($this->request, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_BOUNCED, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company);
@ -263,7 +263,7 @@ class ProcessPostmarkWebhook implements ShouldQueue
$this->request['MessageID']
);
LightLogs::create($spam)->queue();
LightLogs::create($spam)->batch();
SystemLogger::dispatch($this->request, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_SPAM_COMPLAINT, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company);

View File

@ -77,7 +77,12 @@ class PurchaseOrderEmail implements ShouldQueue
/* Mark entity sent */
$invitation->purchase_order->service()->markSent()->save();
$email_builder = (new PurchaseOrderEmailEngine($invitation, 'purchase_order', $this->template_data))->build();
if(is_array($this->template_data) && array_key_exists('template', $this->template_data))
$template = $this->template_data['template'];
else
$template = 'purchase_order';
$email_builder = (new PurchaseOrderEmailEngine($invitation, $template, $this->template_data))->build();
$nmo = new NinjaMailerObject;
$nmo->mailable = new VendorTemplateEmail($email_builder, $invitation->contact, $invitation);
@ -86,7 +91,7 @@ class PurchaseOrderEmail implements ShouldQueue
$nmo->to_user = $invitation->contact;
$nmo->entity_string = 'purchase_order';
$nmo->invitation = $invitation;
$nmo->reminder_template = 'purchase_order';
$nmo->reminder_template = 'email_template_purchase_order';
$nmo->entity = $invitation->purchase_order;
NinjaMailerJob::dispatch($nmo)->delay(5);

View File

@ -73,8 +73,11 @@ class SendRecurring implements ShouldQueue
$invoice->auto_bill_enabled = false;
}
$invoice->date = now()->format('Y-m-d');
$invoice->due_date = $this->recurring_invoice->calculateDueDate(now()->format('Y-m-d'));
$invoice->date = date('Y-m-d');
nlog("Recurring Invoice Date Set on Invoice = {$invoice->date} - ". now()->format('Y-m-d'));
$invoice->due_date = $this->recurring_invoice->calculateDueDate(date('Y-m-d'));
$invoice->recurring_id = $this->recurring_invoice->id;
$invoice->saveQuietly();
@ -96,7 +99,7 @@ class SendRecurring implements ShouldQueue
/* 09-01-2022 ensure we create the PDFs at this point in time! */
$invoice->service()->touchPdf(true);
nlog('updating recurring invoice dates');
//nlog('updating recurring invoice dates');
/* Set next date here to prevent a recurring loop forming */
$this->recurring_invoice->next_send_date = $this->recurring_invoice->nextSendDate();
$this->recurring_invoice->next_send_date_client = $this->recurring_invoice->nextSendDateClient();
@ -108,9 +111,9 @@ class SendRecurring implements ShouldQueue
$this->recurring_invoice->setCompleted();
}
// nlog('next send date = '.$this->recurring_invoice->next_send_date);
//nlog('next send date = '.$this->recurring_invoice->next_send_date);
// nlog('remaining cycles = '.$this->recurring_invoice->remaining_cycles);
// nlog('last send date = '.$this->recurring_invoice->last_sent_date);
//nlog('last send date = '.$this->recurring_invoice->last_sent_date);
$this->recurring_invoice->save();

View File

@ -264,7 +264,7 @@ class Import implements ShouldQueue
$t->replace(Ninja::transformTranslations($this->company->settings));
Mail::to($this->user->email, $this->user->name())
->send(new MigrationCompleted($this->company, implode("<br>",$check_data)));
->send(new MigrationCompleted($this->company->id, $this->company->db, implode("<br>",$check_data)));
}
catch(\Exception $e) {
nlog($e->getMessage());
@ -715,6 +715,15 @@ class Import implements ShouldQueue
Client::reguard();
Client::with('contacts')->where('company_id', $this->company->id)->cursor()->each(function ($client){
$contact = $client->contacts->sortByDesc('is_primary')->first();
$contact->is_primary = true;
$contact->save();
});
/*Improve memory handling by setting everything to null when we have finished*/
$data = null;
$contact_repository = null;

View File

@ -62,7 +62,8 @@ class ReminderJob implements ShouldQueue
{
nlog('Sending invoice reminders '.now()->format('Y-m-d h:i:s'));
Invoice::where('is_deleted', 0)
Invoice::query()
->where('is_deleted', 0)
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
->whereNull('deleted_at')
->where('balance', '>', 0)

View File

@ -329,6 +329,24 @@ class MultiDB
return false;
}
public static function findAndSetDbByInappTransactionId($transaction_id) :bool
{
$current_db = config('database.default');
foreach (self::$dbs as $db) {
if (Account::on($db)->where('inapp_transaction_id', $transaction_id)->exists()) {
self::setDb($db);
return true;
}
}
self::setDB($current_db);
return false;
}
public static function findAndSetDbByContactKey($contact_key) :bool
{
$current_db = config('database.default');

View File

@ -0,0 +1,58 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Listeners\Subscription;
use App\Libraries\MultiDB;
use App\Models\Account;
use Illuminate\Contracts\Queue\ShouldQueue;
use Imdhemy\Purchases\Events\AppStore\DidRenew;
class AppStoreRenewSubscription implements ShouldQueue
{
/**
* Create the event listener.
*
* @param ActivityRepository $activity_repo
*/
public function __construct()
{
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle(DidRenew $event)
{
$inapp_transaction_id = $event->getSubscriptionId(); //$subscription_id
MultiDB::findAndSetDbByInappTransactionId($inapp_transaction_id);
$account = Account::where('inapp_transaction_id', $inapp_transaction_id)->first();
if($account->plan_term == 'month')
$account->plan_expires = now()->addMonth();
elseif($account->plan_term == 'year')
$account->plan_expires = now()->addYear();
$account->save();
// $server_notification = $event->getServerNotification();
// $subscription = $event->getSubscription();
// $subscription_identifier = $event->getSubscriptionIdentifier();
}
}

View File

@ -2,6 +2,7 @@
namespace App\Mail;
use App\Libraries\MultiDB;
use App\Models\Company;
use App\Utils\Ninja;
use Illuminate\Bus\Queueable;
@ -13,19 +14,23 @@ class MigrationCompleted extends Mailable
{
// use Queueable, SerializesModels;
public $company;
public $company_id;
public $db;
public $check_data;
public $company;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct(Company $company, $check_data = '')
public function __construct(int $company_id, string $db, $check_data = '')
{
$this->company = $company;
$this->company_id = $company_id;
$this->check_data = $check_data;
$this->db = $db;
}
/**
@ -35,6 +40,10 @@ class MigrationCompleted extends Mailable
*/
public function build()
{
MultiDB::setDb($this->db);
$this->company = Company::find($this->company_id);
App::forgetInstance('translator');
$t = app('translator');
$t->replace(Ninja::transformTranslations($this->company->settings));

View File

@ -8,6 +8,7 @@ use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use LimitIterator;
use SplFileObject;
use Illuminate\Support\Carbon;
class SupportMessageSent extends Mailable
{
@ -72,8 +73,8 @@ class SupportMessageSent extends Mailable
$plan_status = '';
if(Carbon::parse($account->plan_expires)->lt(now()))
$plan_status = 'Plan Expired';
if($account->plan_expires && Carbon::parse($account->plan_expires)->lt(now()))
$plan_status = 'Plan Expired :: ';
if (Ninja::isHosted()) {
$subject = "{$priority}Hosted-{$db}-{$is_large}{$platform}{$migrated}{$trial} :: {$plan} :: {$plan_status} ".date('M jS, g:ia');

View File

@ -91,7 +91,8 @@ class TemplateEmail extends Mailable
if (strlen($settings->bcc_email) > 1) {
if (Ninja::isHosted()) {
$bccs = explode(',', str_replace(' ', '', $settings->bcc_email));
$this->bcc(reset($bccs)); //remove whitespace if any has been inserted.
$this->bcc(array_slice($bccs, 0, 2));
//$this->bcc(reset($bccs)); //remove whitespace if any has been inserted.
} else {
$this->bcc(explode(',', str_replace(' ', '', $settings->bcc_email)));
}//remove whitespace if any has been inserted.
@ -116,11 +117,6 @@ class TemplateEmail extends Mailable
'whitelabel' => $this->client->user->account->isPaid() ? true : false,
'logo' => $this->company->present()->logo($settings),
]);
// ->withSymfonyMessage(function ($message) use ($company) {
// $message->getHeaders()->addTextHeader('Tag', $company->company_key);
// $message->invitation = $this->invitation;
//});
// ->tag($company->company_key);
/*In the hosted platform we need to slow things down a little for Storage to catch up.*/

View File

@ -61,7 +61,7 @@ class VendorTemplateEmail extends Mailable
}
if ($this->build_email->getTemplate() == 'custom') {
$this->build_email->setBody(str_replace('$body', $this->build_email->getBody(), $this->client->getSetting('email_style_custom')));
$this->build_email->setBody(str_replace('$body', $this->build_email->getBody(), $this->company->getSetting('email_style_custom')));
}
$settings = $this->company->settings;

View File

@ -613,19 +613,19 @@ class Client extends BaseModel implements HasLocalePreference
{
$defaults = [];
if (! (array_key_exists('terms', $data) && strlen($data['terms']) > 1)) {
if (! (array_key_exists('terms', $data) && is_string($data['terms']) && strlen($data['terms']) > 1)) {
$defaults['terms'] = $this->getSetting($entity_name.'_terms');
} elseif (array_key_exists('terms', $data)) {
$defaults['terms'] = $data['terms'];
}
if (! (array_key_exists('footer', $data) && strlen($data['footer']) > 1)) {
if (! (array_key_exists('footer', $data) && is_string($data['footer']) && strlen($data['footer']) > 1)) {
$defaults['footer'] = $this->getSetting($entity_name.'_footer');
} elseif (array_key_exists('footer', $data)) {
$defaults['footer'] = $data['footer'];
}
if (strlen($this->public_notes) >= 1) {
if (is_string($this->public_notes) && strlen($this->public_notes) >= 1) {
$defaults['public_notes'] = $this->public_notes;
}

View File

@ -122,6 +122,7 @@ class Company extends BaseModel
'stock_notification',
'enabled_expense_tax_rates',
'invoice_task_project',
'report_include_deleted',
];
protected $hidden = [

View File

@ -560,7 +560,7 @@ class RecurringInvoice extends BaseModel
break;
case 'on_receipt':
return Carbon::Parse($date)->copy();
return Carbon::parse($date)->copy();
break;
default:

View File

@ -64,7 +64,7 @@ class Vendor extends BaseModel
protected $touches = [];
protected $with = [
'company',
'contacts.company',
];
protected $presenter = VendorPresenter::class;
@ -108,12 +108,13 @@ class Vendor extends BaseModel
}
if (! $this->currency_id) {
$this->currency_id = 1;
return $this->company->currency();
}
return $currencies->filter(function ($item) {
return $item->id == $this->currency_id;
})->first();
}
public function company()

View File

@ -45,10 +45,7 @@ class VendorContact extends Authenticatable implements HasLocalePreference
'hashed_id',
];
protected $with = [
// 'vendor',
// 'company'
];
protected $with = [];
protected $casts = [
'updated_at' => 'timestamp',

View File

@ -70,6 +70,12 @@ class Webhook extends BaseModel
const EVENT_PROJECT_UPDATE = 26;
const EVENT_CREATE_CREDIT = 27;
const EVENT_UPDATE_CREDIT = 28;
const EVENT_DELETE_CREDIT = 29;
public static $valid_events = [
self::EVENT_CREATE_CLIENT,
self::EVENT_CREATE_INVOICE,
@ -97,6 +103,9 @@ class Webhook extends BaseModel
self::EVENT_REMIND_INVOICE,
self::EVENT_PROJECT_CREATE,
self::EVENT_PROJECT_UPDATE,
self::EVENT_CREATE_CREDIT,
self::EVENT_UPDATE_CREDIT,
self::EVENT_DELETE_CREDIT,
];
protected $fillable = [

View File

@ -0,0 +1,86 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Notifications\Ninja;
use App\Models\Account;
use App\Models\Client;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class DomainFailureNotification extends Notification
{
/**
* Create a new notification instance.
*
* @return void
*/
protected string $domain;
public function __construct(string $domain)
{
$this->domain = $domain;
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return ['slack'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
* @return MailMessage
*/
public function toMail($notifiable)
{
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
public function toSlack($notifiable)
{
$content = "Domain Certificate failure:\n";
$content .= "{$this->domain}\n";
return (new SlackMessage)
->success()
->from(ctrans('texts.notification_bot'))
->image('https://app.invoiceninja.com/favicon.png')
->content($content);
}
}

View File

@ -27,6 +27,13 @@ class CreditObserver
*/
public function created(Credit $credit)
{
$subscriptions = Webhook::where('company_id', $credit->company->id)
->where('event_id', Webhook::EVENT_CREATE_CREDIT)
->exists();
if ($subscriptions) {
WebhookHandler::dispatch(Webhook::EVENT_CREATE_CREDIT, $credit, $credit->company)->delay(now()->addSeconds(2));
}
}
/**
@ -37,6 +44,13 @@ class CreditObserver
*/
public function updated(Credit $credit)
{
$subscriptions = Webhook::where('company_id', $credit->company->id)
->where('event_id', Webhook::EVENT_UPDATE_CREDIT)
->exists();
if ($subscriptions) {
WebhookHandler::dispatch(Webhook::EVENT_UPDATE_CREDIT, $credit, $credit->company)->delay(now()->addSeconds(2));
}
}
/**
@ -47,6 +61,13 @@ class CreditObserver
*/
public function deleted(Credit $credit)
{
$subscriptions = Webhook::where('company_id', $credit->company->id)
->where('event_id', Webhook::EVENT_DELETE_CREDIT)
->exists();
if ($subscriptions) {
WebhookHandler::dispatch(Webhook::EVENT_DELETE_CREDIT, $credit, $credit->company)->delay(now()->addSeconds(2));
}
}
/**

View File

@ -39,6 +39,7 @@ class CreditCard
public function authorizeView(array $data)
{
$data['gateway'] = $this->braintree;
$data['threeds_enable'] = $this->braintree->company_gateway->getConfigField('threeds') ? "true" : "false";
return render('gateways.braintree.credit_card.authorize', $data);
}
@ -54,11 +55,32 @@ class CreditCard
* @param array $data
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
private function threeDParameters(array $data)
{
return [
'amount' => $data['amount_with_fee'],
'email' => $this->braintree->client->present()->email(),
'billingAddress' => [
'givenName' => $this->braintree->client->present()->first_name() ?: $this->braintree->client->present()->name(),
'surname' => $this->braintree->client->present()->last_name() ?: '',
'phoneNumber' => $this->braintree->client->present()->phone(),
'streetAddress' => $this->braintree->client->address1 ?: '',
'extendedAddress' =>$this->braintree->client->address2 ?: '',
'locality' => $this->braintree->client->city ?: '',
'postalCode' => $this->braintree->client->postal_code ?: '',
'countryCodeAlpha2' => $this->braintree->client->country ? $this->braintree->client->country->iso_3166_2 : 'US',
]
];
}
public function paymentView(array $data)
{
$data['gateway'] = $this->braintree;
$data['client_token'] = $this->braintree->gateway->clientToken()->generate();
$data['threeds'] = $this->threeDParameters($data);
$data['threeds_enable'] = $this->braintree->company_gateway->getConfigField('threeds') ? "true" : "false";
if ($this->braintree->company_gateway->getConfigField('merchantAccountId')) {
/** https://developer.paypal.com/braintree/docs/reference/request/client-token/generate#merchant_account_id */
$data['client_token'] = $this->braintree->gateway->clientToken()->generate([
@ -78,6 +100,8 @@ class CreditCard
*/
public function paymentResponse(PaymentResponseRequest $request)
{
// nlog($request->all());
$state = [
'server_response' => json_decode($request->gateway_response),
'payment_hash' => $request->payment_hash,

View File

@ -12,6 +12,7 @@
namespace App\PaymentDrivers;
use App\Exceptions\PaymentFailed;
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
use App\Http\Requests\Gateways\Checkout3ds\Checkout3dsRequest;
use App\Http\Requests\Payments\PaymentWebhookRequest;
@ -33,7 +34,8 @@ use Checkout\CheckoutArgumentException;
use Checkout\CheckoutAuthorizationException;
use Checkout\CheckoutDefaultSdk;
use Checkout\CheckoutFourSdk;
use Checkout\Common\CustomerRequest;
use Checkout\Customers\CustomerRequest;
use Checkout\Customers\Four\CustomerRequest as FourCustomerRequest;
use Checkout\Environment;
use Checkout\Library\Exceptions\CheckoutHttpException;
use Checkout\Models\Payments\IdSource;
@ -253,12 +255,13 @@ class CheckoutComPaymentDriver extends BaseDriver
];
} catch (CheckoutApiException $e) {
// API error
$request_id = $e->request_id;
$http_status_code = $e->http_status_code;
$error_details = $e->error_details;
throw new PaymentFailed($e->getMessage(), $e->getCode());
} catch (CheckoutArgumentException $e) {
// Bad arguments
throw new PaymentFailed($e->getMessage(), $e->getCode());
return [
'transaction_reference' => null,
'transaction_response' => json_encode($e->getMessage()),
@ -269,6 +272,8 @@ class CheckoutComPaymentDriver extends BaseDriver
} catch (CheckoutAuthorizationException $e) {
// Bad Invalid authorization
throw new PaymentFailed($e->getMessage(), $e->getCode());
return [
'transaction_reference' => null,
'transaction_response' => json_encode($e->getMessage()),
@ -285,12 +290,28 @@ class CheckoutComPaymentDriver extends BaseDriver
$response = $this->gateway->getCustomersClient()->get($this->client->present()->email());
return $response;
} catch (\Exception $e) {
$request = new CustomerRequest();
if ($this->is_four_api) {
$request = new FourCustomerRequest();
}
else{
$request = new CustomerRequest();
}
$request->email = $this->client->present()->email();
$request->name = $this->client->present()->name();
$request->phone = $this->client->present()->phone();
return $request;
try {
$response = $this->gateway->getCustomersClient()->create($request);
} catch (\Exception $e) {
// API error
throw new PaymentFailed($e->getMessage(), $e->getCode());
}
return $response;
}
}

View File

@ -53,6 +53,8 @@ class ACH
public function authorizeView(array $data)
{
$data['gateway'] = $this->forte;
return render('gateways.forte.ach.authorize', $data);
}
@ -81,7 +83,7 @@ class ACH
$this->forte->payment_hash->data = array_merge((array) $this->forte->payment_hash->data, $data);
$this->forte->payment_hash->save();
$data['gateway'] = $this;
$data['gateway'] = $this->forte;
return render('gateways.forte.ach.pay', $data);
}

View File

@ -261,7 +261,7 @@ class GoCardlessPaymentDriver extends BaseDriver
//finalize payments on invoices here.
}
if ($event['action'] === 'failed') {
if ($event['action'] === 'failed' && array_key_exists('payment', $event['links'])) {
$payment = Payment::query()
->where('transaction_reference', $event['links']['payment'])
->where('company_id', $request->getCompany()->id)

View File

@ -125,6 +125,20 @@ class ACH
$bank_account = Customer::retrieveSource($request->customer, $request->source, [], $this->stripe->stripe_connect_auth);
/* Catch externally validated bank accounts and mark them as verified */
if(property_exists($bank_account, 'status') && $bank_account->status == 'verified'){
$meta = $token->meta;
$meta->state = 'authorized';
$token->meta = $meta;
$token->save();
return redirect()
->route('client.payment_methods.show', $token->hashed_id)
->with('message', __('texts.payment_method_verified'));
}
try {
$bank_account->verify(['amounts' => request()->transactions]);
@ -151,6 +165,18 @@ class ACH
$data['payment_method_id'] = GatewayType::BANK_TRANSFER;
$data['customer'] = $this->stripe->findOrCreateCustomer();
$data['amount'] = $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency());
$amount = $data['total']['amount_with_fee'];
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($this->stripe->payment_hash->invoices(), 'invoice_id')))
->withTrashed()
->first();
if ($invoice) {
$description = "Invoice {$invoice->number} for {$amount} for client {$this->stripe->client->present()->name()}";
} else {
$description = "Payment with no invoice for amount {$amount} for client {$this->stripe->client->present()->name()}";
}
$intent = false;
@ -162,6 +188,11 @@ class ACH
'setup_future_usage' => 'off_session',
'customer' => $data['customer']->id,
'payment_method_types' => ['us_bank_account'],
'description' => $description,
'metadata' => [
'payment_hash' => $this->stripe->payment_hash->hash,
'gateway_type_id' => GatewayType::BANK_TRANSFER,
],
]
);
}

View File

@ -17,11 +17,12 @@ use App\Jobs\Util\SystemLogger;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\Stripe\ACH;
use App\PaymentDrivers\StripePaymentDriver;
use App\PaymentDrivers\Stripe\ACH;
use App\Utils\Ninja;
use App\Utils\Traits\MakesHash;
use Stripe\Exception\ApiConnectionException;
@ -137,8 +138,10 @@ class Charge
if ($cgt->gateway_type_id == GatewayType::SEPA) {
$payment_method_type = PaymentType::SEPA;
$status = Payment::STATUS_PENDING;
} else {
$payment_method_type = $response->charges->data[0]->payment_method_details->card->brand;
$status = Payment::STATUS_COMPLETED;
}
$data = [
@ -148,7 +151,7 @@ class Charge
'amount' => $amount,
];
$payment = $this->stripe->createPayment($data);
$payment = $this->stripe->createPayment($data, $status);
$payment->meta = $cgt->meta;
$payment->save();

View File

@ -13,6 +13,7 @@ namespace App\PaymentDrivers\Stripe\Jobs;
use App\Jobs\Util\SystemLogger;
use App\Libraries\MultiDB;
use App\Models\ClientGatewayToken;
use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\GatewayType;
@ -21,6 +22,7 @@ use App\Models\Payment;
use App\Models\PaymentHash;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\PaymentDrivers\Stripe\UpdatePaymentMethods;
use App\PaymentDrivers\Stripe\Utilities;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
@ -53,7 +55,7 @@ class PaymentIntentWebhook implements ShouldQueue
public function handle()
{
MultiDB::findAndSetDbByCompanyKey($this->company_key);
$company = Company::where('company_key', $this->company_key)->first();
@ -145,7 +147,18 @@ class PaymentIntentWebhook implements ShouldQueue
$this->updateCreditCardPayment($payment_hash, $client);
}
elseif(array_key_exists('payment_method_types', $this->stripe_request['object']) && optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']) && in_array('us_bank_account', $this->stripe_request['object']['payment_method_types']))
{
nlog("hash found");
$hash = $this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash'];
$payment_hash = PaymentHash::where('hash', $hash)->first();
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
$client = $invoice->client;
$this->updateAchPayment($payment_hash, $client);
}
}
@ -161,6 +174,81 @@ class PaymentIntentWebhook implements ShouldQueue
}
private function updateAchPayment($payment_hash, $client)
{
$company_gateway = CompanyGateway::find($this->company_gateway_id);
$payment_method_type = optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['gateway_type_id'];
$driver = $company_gateway->driver($client)->init()->setPaymentMethod($payment_method_type);
$payment_hash->data = array_merge((array) $payment_hash->data, $this->stripe_request);
$payment_hash->save();
$driver->setPaymentHash($payment_hash);
$data = [
'payment_method' => $payment_hash->data->object->payment_method,
'payment_type' => PaymentType::ACH,
'amount' => $payment_hash->data->amount_with_fee,
'transaction_reference' => $this->stripe_request['object']['charges']['data'][0]['id'],
'gateway_type_id' => GatewayType::BANK_TRANSFER,
];
$payment = $driver->createPayment($data, Payment::STATUS_COMPLETED);
SystemLogger::dispatch(
['response' => $this->stripe_request, 'data' => $data],
SystemLog::CATEGORY_GATEWAY_RESPONSE,
SystemLog::EVENT_GATEWAY_SUCCESS,
SystemLog::TYPE_STRIPE,
$client,
$client->company,
);
try {
$customer = $driver->getCustomer($this->stripe_request['object']['charges']['data'][0]['customer']);
$method = $driver->getStripePaymentMethod($this->stripe_request['object']['charges']['data'][0]['payment_method']);
$payment_method = $this->stripe_request['object']['charges']['data'][0]['payment_method'];
$token_exists = ClientGatewayToken::where([
'gateway_customer_reference' => $customer->id,
'token' => $payment_method,
'client_id' => $client->id,
'company_id' => $client->company_id,
])->exists();
/* Already exists return */
if ($token_exists) {
return;
}
$payment_meta = new \stdClass;
$payment_meta->brand = (string) \sprintf('%s (%s)', $method->us_bank_account['bank_name'], ctrans('texts.ach'));
$payment_meta->last4 = (string) $method->us_bank_account['last4'];
$payment_meta->type = GatewayType::BANK_TRANSFER;
$payment_meta->state = 'verified';
$data = [
'payment_meta' => $payment_meta,
'token' => $payment_method,
'payment_method_id' => GatewayType::BANK_TRANSFER,
];
$additional_data = ['gateway_customer_reference' => $customer->id];
if ($customer->default_source === $method->id) {
$additional_data = ['gateway_customer_reference' => $customer->id, 'is_default' => 1];
}
$driver->storeGatewayToken($data, $additional_data);
}
catch(\Exception $e){
nlog("failed to import payment methods");
nlog($e->getMessage());
}
}
private function updateCreditCardPayment($payment_hash, $client)
{
$company_gateway = CompanyGateway::find($this->company_gateway_id);

View File

@ -58,12 +58,12 @@ class UpdateCustomer implements ShouldQueue
$company = Company::where('company_key', $this->company_key)->first();
if($company->id !== config('ninja.ninja_default_company_id'))
return;
$company_gateway = CompanyGateway::find($this->company_gateway_id);
$client = Client::withTrashed()->find($this->client_id);
if(!$company_gateway->update_details)
return;
$stripe = $company_gateway->driver($client)->init();
$customer = $stripe->findOrCreateCustomer();

View File

@ -39,6 +39,7 @@ class SEPA
$data['client'] = $this->stripe->client;
$data['country'] = $this->stripe->client->country->iso_3166_2;
$data['currency'] = $this->stripe->client->currency();
$data['payment_hash'] = 'x';
return render('gateways.stripe.sepa.authorize', $data);
}

View File

@ -70,11 +70,11 @@ class UpdatePaymentMethods
$this->importBankAccounts($customer, $client);
}
private function importBankAccounts($customer, $client)
public function importBankAccounts($customer, $client)
{
$sources = $customer->sources;
if(!property_exists($sources, 'data'))
if(!$customer || !property_exists($sources, 'data'))
return;
foreach ($sources->data as $method) {

View File

@ -511,6 +511,36 @@ class StripePaymentDriver extends BaseDriver
}
}
public function updateCustomer()
{
if($this->client)
{
$customer = $this->findOrCreateCustomer();
//Else create a new record
$data['name'] = $this->client->present()->name();
$data['phone'] = substr($this->client->present()->phone(), 0, 20);
$data['address']['line1'] = $this->client->address1;
$data['address']['line2'] = $this->client->address2;
$data['address']['city'] = $this->client->city;
$data['address']['postal_code'] = $this->client->postal_code;
$data['address']['state'] = $this->client->state;
$data['address']['country'] = $this->client->country ? $this->client->country->iso_3166_2 : '';
$data['shipping']['name'] = $this->client->present()->name();
$data['shipping']['address']['line1'] = $this->client->shipping_address1;
$data['shipping']['address']['line2'] = $this->client->shipping_address2;
$data['shipping']['address']['city'] = $this->client->shipping_city;
$data['shipping']['address']['postal_code'] = $this->client->shipping_postal_code;
$data['shipping']['address']['state'] = $this->client->shipping_state;
$data['shipping']['address']['country'] = $this->client->shipping_country ? $this->client->shipping_country->iso_3166_2 : '';
\Stripe\Customer::update($customer->id, $data, $this->stripe_connect_auth);
}
}
public function refund(Payment $payment, $amount, $return_client_response = false)
{
$this->init();

View File

@ -41,6 +41,7 @@ class ACH
public function authorizeView($data)
{
$data['gateway'] = $this->wepay_payment_driver;
$data['country_code'] = $this->wepay_payment_driver->client ? $this->wepay_payment_driver->client->country->iso_3166_2 : $this->wepay_payment_driver->company_gateway->company()->iso_3166_2;
return render('gateways.wepay.authorize.bank_transfer', $data);
}

View File

@ -37,6 +37,7 @@ class CreditCard
public function authorizeView($data)
{
$data['gateway'] = $this->wepay_payment_driver;
$data['country_code'] = $this->wepay_payment_driver->client ? $this->wepay_payment_driver->client->country->iso_3166_2 : $this->wepay_payment_driver->company_gateway->company()->iso_3166_2;
return render('gateways.wepay.authorize.authorize', $data);
}
@ -101,6 +102,7 @@ class CreditCard
{
$data['gateway'] = $this->wepay_payment_driver;
$data['description'] = ctrans('texts.invoices').': '.collect($data['invoices'])->pluck('invoice_number');
$data['country_code'] = $this->wepay_payment_driver->client ? $this->wepay_payment_driver->client->country->iso_3166_2 : $this->wepay_payment_driver->company_gateway->company()->iso_3166_2;
return render('gateways.wepay.credit_card.pay', $data);
}

View File

@ -27,7 +27,7 @@ class Setup
{
/*
'user_id',
'company',
'user_company',
*/
return render('gateways.wepay.signup.index', $data);

View File

@ -47,7 +47,7 @@ class EntityPolicy
public function edit(User $user, $entity) : bool
{
return ($user->isAdmin() && $entity->company_id == $user->companyId())
|| ($user->hasPermission('edit_'.strtolower(class_basename($entity))) && $entity->company_id == $user->companyId())
|| ($user->hasPermission('edit_'.strtolower(\Illuminate\Support\Str::snake(class_basename($entity)))) && $entity->company_id == $user->companyId())
|| ($user->hasPermission('edit_all') && $entity->company_id == $user->companyId())
|| $user->owns($entity)
|| $user->assigned($entity);
@ -64,7 +64,7 @@ class EntityPolicy
public function view(User $user, $entity) : bool
{
return ($user->isAdmin() && $entity->company_id == $user->companyId())
|| ($user->hasPermission('view_'.strtolower(class_basename($entity))) && $entity->company_id == $user->companyId())
|| ($user->hasPermission('view_'.strtolower(\Illuminate\Support\Str::snake(class_basename($entity)))) && $entity->company_id == $user->companyId())
|| ($user->hasPermission('view_all') && $entity->company_id == $user->companyId())
|| $user->owns($entity)
|| $user->assigned($entity);

View File

@ -304,7 +304,9 @@ class BaseRepository
if (($state['finished_amount'] != $state['starting_amount']) && ($model->status_id != Invoice::STATUS_DRAFT)) {
//10-07-2022
//14-09-2022 log when we make changes to the invoice balance.
nlog("Adjustment - {$model->number} - " .$state['finished_amount']. " - " . $state['starting_amount']);
$model->service()->updateStatus()->save();
$model->client->service()->updateBalance(($state['finished_amount'] - $state['starting_amount']))->save();
$model->ledger()->updateInvoiceBalance(($state['finished_amount'] - $state['starting_amount']), "Update adjustment for invoice {$model->number}");

View File

@ -1,4 +1,5 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
@ -16,6 +17,7 @@ use App\Libraries\Currency\Conversion\CurrencyApi;
use App\Models\Expense;
use App\Utils\Traits\GeneratesCounter;
use Illuminate\Support\Carbon;
use Illuminate\Database\QueryException;
/**
* ExpenseRepository.
@ -24,6 +26,8 @@ class ExpenseRepository extends BaseRepository
{
use GeneratesCounter;
private $completed = true;
/**
* Saves the expense and its contacts.
*
@ -32,15 +36,17 @@ class ExpenseRepository extends BaseRepository
*
* @return \App\Models\Expense|null expense Object
*/
public function save(array $data, Expense $expense) : ?Expense
public function save(array $data, Expense $expense): ?Expense
{
$expense->fill($data);
if (! $expense->id) {
if (!$expense->id) {
$expense = $this->processExchangeRates($data, $expense);
}
$expense->number = empty($expense->number) ? $this->getNextExpenseNumber($expense) : $expense->number;
if (empty($expense->number))
$expense = $this->findAndSaveNumber($expense);
$expense->save();
if (array_key_exists('documents', $data)) {
@ -54,6 +60,7 @@ class ExpenseRepository extends BaseRepository
* Store expenses in bulk.
*
* @param array $expense
*
* @return \App\Models\Expense|null
*/
public function create($expense): ?Expense
@ -64,7 +71,7 @@ class ExpenseRepository extends BaseRepository
);
}
public function processExchangeRates($data, $expense)
public function processExchangeRates($data, $expense): Expense
{
if (array_key_exists('exchange_rate', $data) && isset($data['exchange_rate']) && $data['exchange_rate'] != 1) {
return $expense;
@ -83,4 +90,35 @@ class ExpenseRepository extends BaseRepository
return $expense;
}
/**
* Handle race conditions when creating expense numbers
*
* @param Expense $expense
* @return \App\Models\Expense
*/
private function findAndSaveNumber($expense): Expense
{
$x = 1;
do {
try {
$expense->number = $this->getNextExpenseNumber($expense);
$expense->saveQuietly();
$this->completed = false;
} catch (QueryException $e) {
$x++;
if ($x > 50)
$this->completed = false;
}
} while ($this->completed);
return $expense;
}
}

View File

@ -68,7 +68,6 @@ class InvoiceRepository extends BaseRepository
return $invoice;
}
// $invoice->service()->markDeleted()->handleCancellation()->save();
$invoice = $invoice->service()->markDeleted()->save();
parent::delete($invoice);

View File

@ -144,7 +144,7 @@ class PaymentRepository extends BaseRepository {
$invoice_totals = array_sum(array_column($data['invoices'], 'amount'));
$invoices = Invoice::whereIn('id', array_column($data['invoices'], 'invoice_id'))->get();
$invoices = Invoice::withTrashed()->whereIn('id', array_column($data['invoices'], 'invoice_id'))->get();
$payment->invoices()->saveMany($invoices);

View File

@ -14,6 +14,7 @@ namespace App\Repositories;
use App\Factory\TaskFactory;
use App\Models\Task;
use App\Utils\Traits\GeneratesCounter;
use Illuminate\Database\QueryException;
/**
* TaskRepository.
@ -24,6 +25,8 @@ class TaskRepository extends BaseRepository
public $new_task = true;
private $completed = true;
/**
* Saves the task and its contacts.
*
@ -45,7 +48,7 @@ class TaskRepository extends BaseRepository
$this->setDefaultStatus($task);
}
$task->number = empty($task->number) || ! array_key_exists('number', $data) ? $this->getNextTaskNumber($task) : $data['number'];
$task->number = empty($task->number) || ! array_key_exists('number', $data) ? $this->trySaving($task) : $data['number'];
if (isset($data['description'])) {
$task->description = trim($data['description']);
@ -244,4 +247,34 @@ class TaskRepository extends BaseRepository
return $task;
}
private function trySaving(Task $task)
{
$x=1;
do{
try{
$task->number = $this->getNextTaskNumber($task);
$task->saveQuietly();
$this->completed = false;
}
catch(QueryException $e){
$x++;
if($x>50)
$this->completed = false;
}
}
while($this->completed);
return $task->number;
}
}

View File

@ -33,7 +33,7 @@ class ClientService
\DB::connection(config('database.default'))->transaction(function () use($amount) {
$this->client = Client::where('id', $this->client->id)->lockForUpdate()->first();
$this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first();
$this->client->balance += $amount;
$this->client->save();
@ -49,7 +49,7 @@ class ClientService
\DB::connection(config('database.default'))->transaction(function () use($balance, $paid_to_date) {
$this->client = Client::where('id', $this->client->id)->lockForUpdate()->first();
$this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first();
$this->client->balance += $balance;
$this->client->paid_to_date += $paid_to_date;
$this->client->save();
@ -65,7 +65,7 @@ class ClientService
\DB::connection(config('database.default'))->transaction(function () use($amount) {
$this->client = Client::where('id', $this->client->id)->lockForUpdate()->first();
$this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first();
$this->client->paid_to_date += $amount;
$this->client->save();
@ -83,7 +83,7 @@ class ClientService
public function getCreditBalance() :float
{
$credits = Credit::where('client_id', $this->client->id)
$credits = Credit::withTrashed()->where('client_id', $this->client->id)
->where('is_deleted', false)
->where('balance', '>', 0)
->where(function ($query) {

View File

@ -70,7 +70,6 @@ class ApplyNumber extends AbstractService
$this->invoice->saveQuietly();
$this->completed = false;
}
catch(QueryException $e){
@ -84,5 +83,8 @@ class ApplyNumber extends AbstractService
}
while($this->completed);
return $this;
}
}

View File

@ -55,7 +55,7 @@ class MarkPaid extends AbstractService
\DB::connection(config('database.default'))->transaction(function () {
$this->invoice = Invoice::where('id', $this->invoice->id)->lockForUpdate()->first();
$this->invoice = Invoice::withTrashed()->where('id', $this->invoice->id)->lockForUpdate()->first();
$this->payable_balance = $this->invoice->balance;

View File

@ -44,6 +44,10 @@ class TriggeredActions extends AbstractService
$this->invoice = $this->invoice->service()->markPaid()->save();
}
if ($this->request->has('mark_sent') && $this->request->input('mark_sent') == 'true') {
$this->invoice = $this->invoice->service()->markSent()->save();
}
if ($this->request->has('amount_paid') && is_numeric($this->request->input('amount_paid'))) {
$this->invoice = $this->invoice->service()->applyPaymentAmount($this->request->input('amount_paid'))->save();
}
@ -53,10 +57,6 @@ class TriggeredActions extends AbstractService
$this->sendEmail();
}
if ($this->request->has('mark_sent') && $this->request->input('mark_sent') == 'true') {
$this->invoice = $this->invoice->service()->markSent()->save();
}
if ($this->request->has('cancel') && $this->request->input('cancel') == 'true') {
$this->invoice = $this->invoice->service()->handleCancellation()->save();
}

View File

@ -33,17 +33,29 @@ class DeletePayment
public function run()
{
if ($this->payment->is_deleted) {
return $this->payment;
}
return $this->setStatus(Payment::STATUS_CANCELLED) //sets status of payment
->updateCreditables() //return the credits first
->adjustInvoices()
->updateClient()
->deletePaymentables()
->cleanupPayment()
->save();
\DB::connection(config('database.default'))->transaction(function () {
if ($this->payment->is_deleted) {
return $this->payment;
}
$this->payment = Payment::withTrashed()->where('id', $this->payment->id)->lockForUpdate()->first();
$this->setStatus(Payment::STATUS_CANCELLED) //sets status of payment
->updateCreditables() //return the credits first
->adjustInvoices()
->updateClient()
->deletePaymentables()
->cleanupPayment()
->save();
}, 2);
return $this->payment;
}
private function cleanupPayment()

View File

@ -109,6 +109,8 @@ class RecurringService
if ($request->has('send_now') && $request->input('send_now') == 'true' && $this->recurring_entity->invoices()->count() == 0) {
$this->sendNow();
return $this;
}
if(isset($this->recurring_entity->client))
@ -125,10 +127,12 @@ class RecurringService
if($this->recurring_entity instanceof RecurringInvoice && $this->recurring_entity->status_id == RecurringInvoice::STATUS_DRAFT){
$this->start()->save();
SendRecurring::dispatch($this->recurring_entity, $this->recurring_entity->company->db);
SendRecurring::dispatchSync($this->recurring_entity, $this->recurring_entity->company->db);
}
return $this->recurring_entity;
$this->recurring_entity = $this->recurring_entity->fresh();
return $this;
}

View File

@ -53,6 +53,7 @@ class AccountTransformer extends EntityTransformer
{
return [
'id' => (string) $this->encodePrimaryKey($account->id),
'key' => (string) $this->account->key,
'default_url' => config('ninja.app_url'),
'plan' => $account->getPlan(),
'plan_term' => (string) $account->plan_terms,

View File

@ -185,6 +185,7 @@ class CompanyTransformer extends EntityTransformer
'enable_applying_payments' => (bool) $company->enable_applying_payments,
'enabled_expense_tax_rates' => (int) $company->enabled_expense_tax_rates,
'invoice_task_project' => (bool) $company->invoice_task_project,
'report_include_deleted' => (bool) $company->report_include_deleted,
];
}

View File

@ -114,6 +114,22 @@ class Helpers
return '';
}
// 04-10-2022 Return Early if no reserved keywords are present, this is a very expenseive process
$string_hit = false;
foreach ( [':MONTH',':YEAR',':QUARTER',':WEEK'] as $string )
{
if(stripos($value, $string) !== FALSE) {
$string_hit = true;
}
}
if(!$string_hit)
return $value;
// 04-10-2022 Return Early if no reserved keywords are present, this is a very expenseive process
Carbon::setLocale($entity->locale());
$replacements = [

View File

@ -168,7 +168,7 @@ class HtmlEngine
$data['$invoice.vendor'] = ['value' => $this->entity->vendor->present()->name(), 'label' => ctrans('texts.vendor_name')];
}
if(strlen($this->company->getSetting('qr_iban')) > 5 && strlen($this->company->getSetting('besr_id')) > 1)
if(strlen($this->company->getSetting('qr_iban')) > 5)
{
try{
$data['$swiss_qr'] = ['value' => (new SwissQrGenerator($this->entity, $this->company))->run(), 'label' => ''];
@ -522,6 +522,9 @@ class HtmlEngine
$data['$font_name'] = ['value' => Helpers::resolveFont($this->settings->primary_font)['name'], 'label' => ''];
$data['$font_url'] = ['value' => Helpers::resolveFont($this->settings->primary_font)['url'], 'label' => ''];
$data['$secondary_font_name'] = ['value' => Helpers::resolveFont($this->settings->secondary_font)['name'], 'label' => ''];
$data['$secondary_font_url'] = ['value' => Helpers::resolveFont($this->settings->secondary_font)['url'], 'label' => ''];
$data['$invoiceninja.whitelabel'] = ['value' => 'https://raw.githubusercontent.com/invoiceninja/invoiceninja/v5-develop/public/images/new_logo.png', 'label' => ''];
$data['$primary_color'] = ['value' => $this->settings->primary_color, 'label' => ''];

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