mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
commit
abeac50aba
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@ -35,7 +35,6 @@ jobs:
|
||||
php artisan key:generate
|
||||
php artisan optimize
|
||||
php artisan storage:link
|
||||
php artisan livewire:publish
|
||||
sudo php artisan cache:clear
|
||||
sudo find ./vendor/bin/ -type f -exec chmod +x {} \;
|
||||
sudo find ./ -type d -exec chmod 755 {} \;
|
||||
|
@ -1 +1 @@
|
||||
5.3.79
|
||||
5.3.80
|
@ -37,6 +37,15 @@ class ClientSettings extends BaseSettings
|
||||
'size_id' => 'string',
|
||||
];
|
||||
|
||||
public static $property_casts = [
|
||||
'language_id' => 'string',
|
||||
'currency_id' => 'string',
|
||||
'payment_terms' => 'string',
|
||||
'valid_until' => 'string',
|
||||
'default_task_rate' => 'float',
|
||||
'send_reminders' => 'bool',
|
||||
];
|
||||
|
||||
/**
|
||||
* Cast object values and return entire class
|
||||
* prevents missing properties from not being returned
|
||||
|
@ -503,6 +503,7 @@ class CompanySettings extends BaseSettings
|
||||
'language_id' => 'string',
|
||||
'show_currency_code' => 'bool',
|
||||
'website' => 'string',
|
||||
'default_task_rate' => 'float',
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -28,7 +28,11 @@ class CloneQuoteToInvoiceFactory
|
||||
unset($quote_array['invoice_id']);
|
||||
unset($quote_array['id']);
|
||||
unset($quote_array['invitations']);
|
||||
unset($quote_array['terms']);
|
||||
|
||||
//preserve terms if they exist on Quotes
|
||||
if(array_key_exists('terms', $quote_array) && strlen($quote_array['terms']) < 2)
|
||||
unset($quote_array['terms']);
|
||||
|
||||
// unset($quote_array['public_notes']);
|
||||
unset($quote_array['footer']);
|
||||
unset($quote_array['design_id']);
|
||||
|
@ -24,7 +24,6 @@ class CompanyGatewayFactory
|
||||
$company_gateway->require_billing_address = false;
|
||||
$company_gateway->require_shipping_address = false;
|
||||
$company_gateway->config = encrypt(json_encode(new \stdClass));
|
||||
// $company_gateway->fees_and_limits = new FeesAndLimits;
|
||||
|
||||
return $company_gateway;
|
||||
}
|
||||
|
@ -48,6 +48,16 @@ class RecurringInvoiceToInvoiceFactory
|
||||
$invoice->custom_value4 = $recurring_invoice->custom_value4;
|
||||
$invoice->amount = $recurring_invoice->amount;
|
||||
$invoice->uses_inclusive_taxes = $recurring_invoice->uses_inclusive_taxes;
|
||||
|
||||
$invoice->custom_surcharge1 = $recurring_invoice->custom_surcharge1;
|
||||
$invoice->custom_surcharge2 = $recurring_invoice->custom_surcharge2;
|
||||
$invoice->custom_surcharge3 = $recurring_invoice->custom_surcharge3;
|
||||
$invoice->custom_surcharge4 = $recurring_invoice->custom_surcharge4;
|
||||
$invoice->custom_surcharge_tax1 = $recurring_invoice->custom_surcharge_tax1;
|
||||
$invoice->custom_surcharge_tax2 = $recurring_invoice->custom_surcharge_tax2;
|
||||
$invoice->custom_surcharge_tax3 = $recurring_invoice->custom_surcharge_tax3;
|
||||
$invoice->custom_surcharge_tax4 = $recurring_invoice->custom_surcharge_tax4;
|
||||
|
||||
// $invoice->balance = $recurring_invoice->balance;
|
||||
$invoice->user_id = $recurring_invoice->user_id;
|
||||
$invoice->assigned_user_id = $recurring_invoice->assigned_user_id;
|
||||
|
@ -168,7 +168,7 @@ abstract class QueryFilters
|
||||
|
||||
public function created_at($value)
|
||||
{
|
||||
$created_at = $value ? $value : 0;
|
||||
$created_at = $value ? (int)$value : 0;
|
||||
|
||||
$created_at = date('Y-m-d H:i:s', $value);
|
||||
|
||||
|
@ -218,7 +218,7 @@ class BaseController extends Controller
|
||||
$query->with(
|
||||
[
|
||||
'company' => function ($query) use ($updated_at, $user) {
|
||||
$query->whereNotNull('updated_at')->with('documents')->with('users');
|
||||
$query->whereNotNull('updated_at')->with('documents','users');
|
||||
},
|
||||
'company.clients' => function ($query) use ($updated_at, $user) {
|
||||
$query->where('clients.updated_at', '>=', $updated_at)->with('contacts.company', 'gateway_tokens', 'documents');
|
||||
@ -392,7 +392,7 @@ class BaseController extends Controller
|
||||
$query->with(
|
||||
[
|
||||
'company' => function ($query) use ($created_at, $user) {
|
||||
$query->whereNotNull('created_at')->with('documents');
|
||||
$query->whereNotNull('created_at')->with('documents','users');
|
||||
},
|
||||
'company.designs'=> function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at)->with('company');
|
||||
@ -466,7 +466,7 @@ class BaseController extends Controller
|
||||
$query->with(
|
||||
[
|
||||
'company' => function ($query) use ($created_at, $user) {
|
||||
$query->whereNotNull('created_at')->with('documents');
|
||||
$query->whereNotNull('created_at')->with('documents','users');
|
||||
},
|
||||
'company.clients' => function ($query) use ($created_at, $user) {
|
||||
$query->where('clients.created_at', '>=', $created_at)->with('contacts.company', 'gateway_tokens', 'documents');
|
||||
@ -500,9 +500,6 @@ class BaseController extends Controller
|
||||
},
|
||||
'company.groups' => function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at)->with('documents');
|
||||
|
||||
// if(!$user->isAdmin())
|
||||
// $query->where('group_settings.user_id', $user->id);
|
||||
},
|
||||
'company.invoices'=> function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at)->with('invitations', 'documents');
|
||||
@ -583,13 +580,30 @@ class BaseController extends Controller
|
||||
$query->where('activities.user_id', $user->id);
|
||||
|
||||
},
|
||||
'company.webhooks'=> function ($query) use($user) {
|
||||
|
||||
if(!$user->isAdmin())
|
||||
$query->where('webhooks.user_id', $user->id);
|
||||
|
||||
},
|
||||
'company.tokens'=> function ($query) use($user) {
|
||||
$query->where('company_tokens.user_id', $user->id);
|
||||
},
|
||||
'company.system_logs',
|
||||
'company.subscriptions'=> function ($query) use($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at);
|
||||
|
||||
if(!$user->isAdmin())
|
||||
$query->where('subscriptions.user_id', $user->id);
|
||||
|
||||
}
|
||||
},
|
||||
'company.recurring_expenses'=> function ($query) use ($created_at, $user) {
|
||||
$query->where('created_at', '>=', $created_at)->with('documents');
|
||||
|
||||
if(!$user->hasPermission('view_recurring_expense'))
|
||||
$query->where('recurring_expenses.user_id', $user->id)->orWhere('recurring_expenses.assigned_user_id', $user->id);
|
||||
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -21,6 +21,15 @@ class SelfUpdateController extends BaseController
|
||||
{
|
||||
use DispatchesJobs;
|
||||
|
||||
private array $purge_file_list = [
|
||||
'bootstrap/cache/compiled.php',
|
||||
'bootstrap/cache/config.php',
|
||||
'bootstrap/cache/packages.php',
|
||||
'bootstrap/cache/services.php',
|
||||
'bootstrap/cache/routes-v7.php',
|
||||
'bootstrap/cache/livewire-components.php',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
@ -117,10 +126,12 @@ class SelfUpdateController extends BaseController
|
||||
|
||||
unlink($file);
|
||||
|
||||
$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); }
|
||||
foreach($this->purge_file_list as $purge_file_path)
|
||||
{
|
||||
$purge_file = base_path($purge_file_path);
|
||||
if (file_exists($purge_file)) { unlink ($purge_file); }
|
||||
|
||||
}
|
||||
|
||||
Artisan::call('clear-compiled');
|
||||
Artisan::call('route:clear');
|
||||
|
@ -36,7 +36,7 @@ class StoreClientRequest extends Request
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
{nlog($this->input);
|
||||
if ($this->input('documents') && is_array($this->input('documents'))) {
|
||||
$documents = count($this->input('documents'));
|
||||
|
||||
@ -95,6 +95,10 @@ class StoreClientRequest extends Request
|
||||
|
||||
if (array_key_exists('settings', $input) && ! empty($input['settings'])) {
|
||||
foreach ($input['settings'] as $key => $value) {
|
||||
|
||||
if($key == 'default_task_rate')
|
||||
$value = floatval($value);
|
||||
|
||||
$settings->{$key} = $value;
|
||||
}
|
||||
}
|
||||
|
@ -157,6 +157,10 @@ class UpdateClientRequest extends Request
|
||||
if (! array_key_exists($key, $saveable_casts)) {
|
||||
unset($settings->{$key});
|
||||
}
|
||||
|
||||
if($key == 'default_task_rate'){
|
||||
$settings->default_task_rate = floatval($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $settings;
|
||||
|
@ -140,7 +140,7 @@ class CompanyImport implements ShouldQueue
|
||||
'expenses',
|
||||
'tasks',
|
||||
'payments',
|
||||
'activities',
|
||||
// 'activities',
|
||||
// 'backups',
|
||||
'company_ledger',
|
||||
'designs',
|
||||
|
@ -84,7 +84,7 @@ class ReminderJob implements ShouldQueue
|
||||
|
||||
//check if this reminder needs to be emailed
|
||||
//15-01-2022 - insert addition if block if send_reminders is definitely set
|
||||
if(in_array($reminder_template, ['reminder1','reminder2','reminder3','reminder_endless']) && $invoice->client->getSetting("enable_".$reminder_template) && $invoice->client->getSetting("send_reminders"))
|
||||
if(in_array($reminder_template, ['reminder1','reminder2','reminder3','reminder_endless']) && $invoice->client->getSetting("enable_".$reminder_template) && $invoice->client->getSetting("send_reminders") && $invoice->company->account->isPaidHostedClient())
|
||||
{
|
||||
$invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) {
|
||||
EmailEntity::dispatch($invitation, $invitation->company, $reminder_template);
|
||||
|
@ -190,7 +190,7 @@ class BaseModel extends Model
|
||||
|
||||
public function numberFormatter()
|
||||
{
|
||||
$number = strlen($this->number) >= 1 ? $this->number : class_basename($this) . "_" . Str::random(5); ;
|
||||
$number = strlen($this->number) >= 1 ? $this->number : class_basename($this) . "_" . Str::random(5);
|
||||
|
||||
$formatted_number = mb_ereg_replace("([^\w\s\d\-_~,;\[\]\(\).])", '', $number);
|
||||
// Remove any runs of periods (thanks falstro!)
|
||||
|
@ -52,6 +52,8 @@ class CompanyUser extends Pivot
|
||||
|
||||
protected $touches = ['user'];
|
||||
|
||||
protected $with = ['user','account'];
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return self::class;
|
||||
|
@ -42,6 +42,9 @@ class Webhook extends BaseModel
|
||||
const EVENT_LATE_INVOICE = 22;
|
||||
const EVENT_EXPIRED_QUOTE = 23;
|
||||
const EVENT_REMIND_INVOICE = 24;
|
||||
const EVENT_PROJECT_CREATE = 25;
|
||||
const EVENT_PROJECT_UPDATE = 26;
|
||||
|
||||
|
||||
public static $valid_events = [
|
||||
self::EVENT_CREATE_CLIENT,
|
||||
@ -68,6 +71,8 @@ class Webhook extends BaseModel
|
||||
self::EVENT_LATE_INVOICE,
|
||||
self::EVENT_EXPIRED_QUOTE,
|
||||
self::EVENT_REMIND_INVOICE,
|
||||
self::EVENT_PROJECT_CREATE,
|
||||
self::EVENT_PROJECT_UPDATE
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
|
89
app/Observers/ProjectObserver.php
Normal file
89
app/Observers/ProjectObserver.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?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\Observers;
|
||||
|
||||
use App\Jobs\Util\WebhookHandler;
|
||||
use App\Models\Project;
|
||||
use App\Models\Webhook;
|
||||
|
||||
class ProjectObserver
|
||||
{
|
||||
public $afterCommit = true;
|
||||
/**
|
||||
* Handle the product "created" event.
|
||||
*
|
||||
* @param Project $project
|
||||
* @return void
|
||||
*/
|
||||
public function created(Project $project)
|
||||
{
|
||||
$subscriptions = Webhook::where('company_id', $project->company_id)
|
||||
->where('event_id', Webhook::EVENT_PROJECT_CREATE)
|
||||
->exists();
|
||||
|
||||
if ($subscriptions) {
|
||||
|
||||
WebhookHandler::dispatch(Webhook::EVENT_PROJECT_CREATE, $project, $project->company, 'client');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the product "updated" event.
|
||||
*
|
||||
* @param Project $project
|
||||
* @return void
|
||||
*/
|
||||
public function updated(Project $project)
|
||||
{
|
||||
$subscriptions = Webhook::where('company_id', $project->company_id)
|
||||
->where('event_id', Webhook::EVENT_PROJECT_UPDATE)
|
||||
->exists();
|
||||
|
||||
if ($subscriptions) {
|
||||
|
||||
WebhookHandler::dispatch(Webhook::EVENT_PROJECT_UPDATE, $project, $project->company, 'client');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the product "deleted" event.
|
||||
*
|
||||
* @param Project $project
|
||||
* @return void
|
||||
*/
|
||||
public function deleted(Project $project)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the product "restored" event.
|
||||
*
|
||||
* @param Project $project
|
||||
* @return void
|
||||
*/
|
||||
public function restored(Project $project)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the product "force deleted" event.
|
||||
*
|
||||
* @param Project $project
|
||||
* @return void
|
||||
*/
|
||||
public function forceDeleted(Project $project)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
@ -164,13 +164,13 @@ class AuthorizePaymentMethod
|
||||
if ($contact) {
|
||||
// Create the Bill To info for new payment type
|
||||
$billto = new CustomerAddressType();
|
||||
$billto->setFirstName($contact->present()->first_name());
|
||||
$billto->setLastName($contact->present()->last_name());
|
||||
$billto->setCompany($this->authorize->client->present()->name());
|
||||
$billto->setAddress($this->authorize->client->address1);
|
||||
$billto->setCity($this->authorize->client->city);
|
||||
$billto->setState($this->authorize->client->state);
|
||||
$billto->setZip($this->authorize->client->postal_code);
|
||||
$billto->setFirstName(substr(0,50,$contact->present()->first_name()));
|
||||
$billto->setLastName(substr(0,50,$contact->present()->last_name()));
|
||||
$billto->setCompany(substr(0,50,$this->authorize->client->present()->name()));
|
||||
$billto->setAddress(substr(0,60,$this->authorize->client->address1));
|
||||
$billto->setCity(substr(0,40,$this->authorize->client->city));
|
||||
$billto->setState(substr(0,40,$this->authorize->client->state));
|
||||
$billto->setZip(substr(0,20,$this->authorize->client->postal_code));
|
||||
|
||||
if ($this->authorize->client->country_id) {
|
||||
$billto->setCountry($this->authorize->client->country->name);
|
||||
|
@ -170,16 +170,52 @@ class CreditCard
|
||||
|
||||
$this->logResponse($response);
|
||||
|
||||
$response_status = ErrorCode::getStatus($response->ResponseMessage);
|
||||
// if(!$response || !property_exists($response, 'ResponseMessage'))
|
||||
// throw new PaymentFailed('The gateway did not return a valid response. Please check your gateway credentials.', 400);
|
||||
|
||||
if(!$response_status['success']){
|
||||
// $response_status = ErrorCode::getStatus($response->ResponseMessage);
|
||||
|
||||
$this->eway_driver->sendFailureMail($response_status['message']);
|
||||
// if(!$response_status['success']){
|
||||
|
||||
throw new PaymentFailed($response_status['message'], 400);
|
||||
}
|
||||
// if($response->getErrors())
|
||||
// {
|
||||
// $message = false;
|
||||
|
||||
// foreach ($response->getErrors() as $error) {
|
||||
// $message = \Eway\Rapid::getMessage($error);
|
||||
// }
|
||||
|
||||
// $return_message = $message ?: $response_status['message'];
|
||||
// }
|
||||
|
||||
// $this->eway_driver->sendFailureMail($response_status['message']);
|
||||
|
||||
// throw new PaymentFailed($response_status['message'], 400);
|
||||
// }
|
||||
|
||||
if($response->TransactionStatus)
|
||||
$payment = $this->storePayment($response);
|
||||
else {
|
||||
|
||||
$message = 'Error processing payment.';
|
||||
|
||||
if(isset($response->ResponseMessage))
|
||||
$message .= " Gateway Error Code = {$response->ResponseMessage}";
|
||||
|
||||
if($response->getErrors())
|
||||
{
|
||||
|
||||
foreach ($response->getErrors() as $error) {
|
||||
$message = \Eway\Rapid::getMessage($error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->eway_driver->sendFailureMail($message);
|
||||
|
||||
throw new PaymentFailed($message, 400);
|
||||
}
|
||||
|
||||
$payment = $this->storePayment($response);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
|
||||
|
||||
@ -257,20 +293,32 @@ class CreditCard
|
||||
|
||||
$response = $this->eway_driver->init()->eway->createTransaction(\Eway\Rapid\Enum\ApiMethod::DIRECT, $transaction);
|
||||
|
||||
$response_status = ErrorCode::getStatus($response->ResponseMessage);
|
||||
if($response->TransactionStatus){
|
||||
$this->logResponse($response, true);
|
||||
$payment = $this->storePayment($response);
|
||||
}
|
||||
else {
|
||||
|
||||
if(!$response_status['success']){
|
||||
$message = 'Error processing payment.';
|
||||
|
||||
$this->logResponse($response, false);
|
||||
if(isset($response->ResponseMessage))
|
||||
$message .= " Gateway Error Code = {$response->ResponseMessage}";
|
||||
|
||||
$this->eway_driver->sendFailureMail($response_status['message']);
|
||||
if($response->getErrors())
|
||||
{
|
||||
|
||||
throw new PaymentFailed($response_status['message'], 400);
|
||||
}
|
||||
foreach ($response->getErrors() as $error) {
|
||||
$message = \Eway\Rapid::getMessage($error);
|
||||
}
|
||||
|
||||
$this->logResponse($response, true);
|
||||
}
|
||||
|
||||
$payment = $this->storePayment($response);
|
||||
$this->logResponse($response, false);
|
||||
|
||||
$this->eway_driver->sendFailureMail($message);
|
||||
|
||||
throw new PaymentFailed($message, 400);
|
||||
}
|
||||
|
||||
return $payment;
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ class PayPalExpressPaymentDriver extends BaseDriver
|
||||
|
||||
private $omnipay_gateway;
|
||||
|
||||
private float $fee = 0;
|
||||
|
||||
const SYSTEM_LOG_TYPE = SystemLog::TYPE_PAYPAL;
|
||||
|
||||
public function gatewayTypes()
|
||||
@ -173,11 +175,17 @@ class PayPalExpressPaymentDriver extends BaseDriver
|
||||
|
||||
public function generatePaymentDetails(array $data)
|
||||
{
|
||||
|
||||
$_invoice = collect($this->payment_hash->data->invoices)->first();
|
||||
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
|
||||
|
||||
$this->fee = $this->feeCalc($invoice, $data['total']['amount_with_fee']);
|
||||
|
||||
return [
|
||||
'currency' => $this->client->getCurrencyCode(),
|
||||
'transactionType' => 'Purchase',
|
||||
'clientIp' => request()->getClientIp(),
|
||||
'amount' => $data['total']['amount_with_fee'],
|
||||
'amount' => $data['total']['amount_with_fee'] + $this->fee,
|
||||
'returnUrl' => route('client.payments.response', [
|
||||
'company_gateway_id' => $this->company_gateway->id,
|
||||
'payment_hash' => $this->payment_hash->hash,
|
||||
@ -200,8 +208,6 @@ class PayPalExpressPaymentDriver extends BaseDriver
|
||||
$_invoice = collect($this->payment_hash->data->invoices)->first();
|
||||
$invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($_invoice->invoice_id));
|
||||
|
||||
$line_item = collect($invoice->line_items)->first();
|
||||
|
||||
$items = [];
|
||||
|
||||
$items[] = new Item([
|
||||
@ -211,8 +217,44 @@ class PayPalExpressPaymentDriver extends BaseDriver
|
||||
'quantity' => 1,
|
||||
]);
|
||||
|
||||
|
||||
if($this->fee > 0.1){
|
||||
|
||||
$items[] = new Item([
|
||||
'name' => " ",
|
||||
'description' => ctrans('texts.gateway_fee_description'),
|
||||
'price' => $this->fee,
|
||||
'quantity' => 1,
|
||||
]);
|
||||
|
||||
|
||||
}
|
||||
|
||||
return $items;
|
||||
|
||||
}
|
||||
|
||||
private function feeCalc($invoice, $invoice_total)
|
||||
{
|
||||
|
||||
$invoice->service()->removeUnpaidGatewayFees();
|
||||
$invoice = $invoice->fresh();
|
||||
|
||||
$balance = floatval($invoice->balance);
|
||||
|
||||
$_updated_invoice = $invoice->service()->addGatewayFee($this->company_gateway, GatewayType::PAYPAL, $invoice_total)->save();
|
||||
|
||||
if(floatval($_updated_invoice->balance) > $balance){
|
||||
|
||||
$fee = floatval($_updated_invoice->balance) - $balance;
|
||||
|
||||
$this->payment_hash->fee_total = $fee;
|
||||
$this->payment_hash->save();
|
||||
|
||||
return $fee;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ use Illuminate\Support\Facades\Queue;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Livewire\Livewire;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
@ -69,6 +70,11 @@ class AppServiceProvider extends ServiceProvider
|
||||
|
||||
app()->instance(TruthSource::class, new TruthSource());
|
||||
|
||||
|
||||
// Model::preventLazyLoading(
|
||||
// !$this->app->isProduction()
|
||||
// );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -213,6 +213,7 @@ use App\Models\Expense;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Product;
|
||||
use App\Models\Project;
|
||||
use App\Models\Proposal;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Subscription;
|
||||
@ -228,6 +229,7 @@ use App\Observers\ExpenseObserver;
|
||||
use App\Observers\InvoiceObserver;
|
||||
use App\Observers\PaymentObserver;
|
||||
use App\Observers\ProductObserver;
|
||||
use App\Observers\ProjectObserver;
|
||||
use App\Observers\ProposalObserver;
|
||||
use App\Observers\QuoteObserver;
|
||||
use App\Observers\SubscriptionObserver;
|
||||
@ -586,6 +588,7 @@ class EventServiceProvider extends ServiceProvider
|
||||
Invoice::observe(InvoiceObserver::class);
|
||||
Payment::observe(PaymentObserver::class);
|
||||
Product::observe(ProductObserver::class);
|
||||
Project::observe(ProjectObserver::class);
|
||||
Proposal::observe(ProposalObserver::class);
|
||||
Quote::observe(QuoteObserver::class);
|
||||
Task::observe(TaskObserver::class);
|
||||
|
@ -19,20 +19,14 @@ use App\Models\Client;
|
||||
use App\Models\Design;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\Product;
|
||||
use App\Services\PdfMaker\Design as PdfMakerDesign;
|
||||
use App\Services\PdfMaker\PdfMaker;
|
||||
use App\Utils\HostedPDF\NinjaPdf;
|
||||
use App\Utils\HtmlEngine;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\PhantomJS\Phantom;
|
||||
use App\Utils\Traits\Pdf\PdfMaker as PdfMakerTrait;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\LazyCollection;
|
||||
|
||||
class Statement
|
||||
{
|
||||
@ -231,7 +225,7 @@ class Statement
|
||||
->where('client_id', $this->client->id)
|
||||
->whereIn('status_id', $this->invoiceStatuses())
|
||||
->whereBetween('date', [Carbon::parse($this->options['start_date']), Carbon::parse($this->options['end_date'])])
|
||||
->orderBy('date', 'ASC')
|
||||
->orderBy('due_date', 'ASC')
|
||||
->cursor();
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ class CompanyUserTransformer extends EntityTransformer
|
||||
|
||||
public function includeToken(CompanyUser $company_user)
|
||||
{
|
||||
$token = $company_user->tokens->where('company_id', $company_user->company_id)->where('user_id', $company_user->user_id)->first();
|
||||
$token = $company_user->tokens()->where('company_id', $company_user->company_id)->where('user_id', $company_user->user_id)->first();
|
||||
|
||||
$transformer = new CompanyTokenTransformer($this->serializer);
|
||||
|
||||
|
@ -122,6 +122,9 @@ class HtmlEngine
|
||||
$data['$invoice.date'] = &$data['$date'];
|
||||
$data['$invoiceDate'] = &$data['$date'];
|
||||
$data['$due_date'] = ['value' => $this->translateDate($this->entity->due_date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')];
|
||||
|
||||
$data['$partial_due_date'] = ['value' => $this->translateDate($this->entity->partial_due_date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.'.$this->entity_string.'_due_date')];
|
||||
|
||||
$data['$dueDate'] = &$data['$due_date'];
|
||||
|
||||
$data['$payment_due'] = ['value' => $this->translateDate($this->entity->due_date, $this->client->date_format(), $this->client->locale()) ?: ' ', 'label' => ctrans('texts.payment_due')];
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Utils\Traits;
|
||||
|
||||
use App\DataMapper\ClientSettings;
|
||||
use App\DataMapper\CompanySettings;
|
||||
use stdClass;
|
||||
|
||||
@ -63,15 +64,6 @@ trait ClientGroupSettingsSaver
|
||||
$entity_settings->{$key} = $value;
|
||||
}
|
||||
|
||||
//this pass will handle any null values that are in the translations
|
||||
// foreach ($settings->translations as $key => $value) {
|
||||
// if (is_null($settings->translations[$key])) {
|
||||
// $settings->translations[$key] = '';
|
||||
// }
|
||||
// }
|
||||
|
||||
// $entity_settings->translations = $settings->translations;
|
||||
|
||||
$entity->settings = $entity_settings;
|
||||
$entity->save();
|
||||
|
||||
@ -121,8 +113,12 @@ trait ClientGroupSettingsSaver
|
||||
continue;
|
||||
}
|
||||
/*Separate loop if it is a _id field which is an integer cast as a string*/
|
||||
elseif (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter') {
|
||||
$value = 'integer';
|
||||
elseif (substr($key, -3) == '_id' ||
|
||||
substr($key, -14) == 'number_counter' ||
|
||||
($key == 'payment_terms' && property_exists($settings, 'payment_terms') && strlen($settings->{$key}) >= 1) ||
|
||||
($key == 'valid_until' && property_exists($settings, 'valid_until') && strlen($settings->{$key}) >= 1)) {
|
||||
|
||||
$value = 'integer';
|
||||
|
||||
if (! property_exists($settings, $key)) {
|
||||
continue;
|
||||
@ -170,7 +166,11 @@ trait ClientGroupSettingsSaver
|
||||
}
|
||||
|
||||
/*Separate loop if it is a _id field which is an integer cast as a string*/
|
||||
if (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter') {
|
||||
if (substr($key, -3) == '_id' ||
|
||||
substr($key, -14) == 'number_counter' ||
|
||||
($key == 'payment_terms' && property_exists($settings, 'payment_terms') && strlen($settings->{$key}) >= 1) ||
|
||||
($key == 'valid_until' && property_exists($settings, 'valid_until') && strlen($settings->{$key}) >= 1)) {
|
||||
|
||||
$value = 'integer';
|
||||
|
||||
if (! property_exists($settings, $key)) {
|
||||
@ -219,8 +219,7 @@ trait ClientGroupSettingsSaver
|
||||
switch ($key) {
|
||||
case 'int':
|
||||
case 'integer':
|
||||
// return ctype_digit(strval(abs($value)));
|
||||
return ctype_digit(strval($value));
|
||||
return is_numeric($value) && ctype_digit(strval(abs($value)));
|
||||
case 'real':
|
||||
case 'float':
|
||||
case 'double':
|
||||
|
@ -32,7 +32,7 @@ trait UserNotifies
|
||||
$notifiable_methods = [];
|
||||
$notifications = $company_user->notifications;
|
||||
|
||||
if ($company_user->company->is_disabled && is_array($notifications->email) || $company_user->trashed() || $company_user->user->trashed()) {
|
||||
if ($invitation->company->is_disabled && is_array($notifications->email) || $company_user->trashed() || $company_user->user->trashed()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ trait UserNotifies
|
||||
$notifiable_methods = [];
|
||||
$notifications = $company_user->notifications;
|
||||
|
||||
if ($company_user->company->is_disabled || ! $notifications || $company_user->trashed() || $company_user->user->trashed()) {
|
||||
if ($entity->company->is_disabled || ! $notifications || $company_user->trashed() || $company_user->user->trashed()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ trait SettingsSaver
|
||||
continue;
|
||||
}
|
||||
/*Separate loop if it is a _id field which is an integer cast as a string*/
|
||||
elseif (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter') {
|
||||
elseif (substr($key, -3) == '_id' || substr($key, -14) == 'number_counter' || ($key == 'payment_terms' && strlen($settings->{$key}) >= 1) || ($key == 'valid_until' && strlen($settings->{$key}) >= 1)) {
|
||||
$value = 'integer';
|
||||
|
||||
if($key == 'gmail_sending_user_id')
|
||||
@ -94,12 +94,11 @@ trait SettingsSaver
|
||||
switch ($key) {
|
||||
case 'int':
|
||||
case 'integer':
|
||||
return ctype_digit(strval(abs($value)));
|
||||
return is_numeric($value) && ctype_digit(strval(abs($value)));
|
||||
case 'real':
|
||||
case 'float':
|
||||
case 'double':
|
||||
return !is_string($value) && (is_float($value) || is_numeric(strval($value)));
|
||||
// return is_float($value) || is_numeric(strval($value));
|
||||
case 'string':
|
||||
return !is_int($value) || ( is_string( $value ) && method_exists($value, '__toString') ) || is_null($value) || is_string($value);
|
||||
case 'bool':
|
||||
|
@ -54,7 +54,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'asset_url' => null,
|
||||
'asset_url' => env('ASSET_URL', null),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -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.79',
|
||||
'app_tag' => '5.3.79',
|
||||
'app_version' => '5.3.80',
|
||||
'app_tag' => '5.3.80',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
@ -50,8 +50,6 @@ class CompanySettingsTest extends TestCase
|
||||
|
||||
$this->company->saveSettings($settings, $this->company);
|
||||
|
||||
//$this->withoutExceptionHandling();
|
||||
|
||||
$response = false;
|
||||
|
||||
try {
|
||||
|
@ -36,7 +36,7 @@ class UpdateCompanyUserTest extends TestCase
|
||||
|
||||
public function testUpdatingCompanyUserAsAdmin()
|
||||
{
|
||||
User::unguard();
|
||||
// User::unguard();
|
||||
|
||||
$settings = new \stdClass;
|
||||
$settings->invoice = 'ninja';
|
||||
|
337
tests/Unit/ClientSettingsTest.php
Normal file
337
tests/Unit/ClientSettingsTest.php
Normal file
@ -0,0 +1,337 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\DataMapper\ClientSettings;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
class ClientSettingsTest extends TestCase
|
||||
{
|
||||
use MockAccountData;
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
$this->faker = \Faker\Factory::create();
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testClientBaseline()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'name' => $this->faker->firstName,
|
||||
'id_number' => 'Coolio',
|
||||
];
|
||||
|
||||
$response = false;
|
||||
|
||||
try{
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/clients/', $data);
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($message);
|
||||
}
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals("1", $arr['data']['settings']['currency_id']);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testClientValidSettings()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'name' => $this->faker->firstName,
|
||||
'id_number' => 'Coolio',
|
||||
'settings' => [
|
||||
'currency_id' => '1',
|
||||
'language_id' => '1',
|
||||
'payment_terms' => '1',
|
||||
'valid_until' => '1',
|
||||
'default_task_rate' => 10,
|
||||
'send_reminders' => true
|
||||
]
|
||||
];
|
||||
|
||||
$response = false;
|
||||
|
||||
try{
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/clients/', $data);
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($message);
|
||||
}
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals("1", $arr['data']['settings']['currency_id']);
|
||||
$this->assertEquals("1", $arr['data']['settings']['language_id']);
|
||||
$this->assertEquals("1", $arr['data']['settings']['payment_terms']);
|
||||
$this->assertEquals(10, $arr['data']['settings']['default_task_rate']);
|
||||
$this->assertEquals(true, $arr['data']['settings']['send_reminders']);
|
||||
$this->assertEquals("1", $arr['data']['settings']['valid_until']);
|
||||
|
||||
}
|
||||
|
||||
public function testClientIllegalCurrency()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'name' => $this->faker->firstName,
|
||||
'id_number' => 'Coolio',
|
||||
'settings' => [
|
||||
'currency_id' => 'a',
|
||||
'language_id' => '1',
|
||||
'payment_terms' => '1',
|
||||
'valid_until' => '1',
|
||||
'default_task_rate' => 10,
|
||||
'send_reminders' => true
|
||||
]
|
||||
];
|
||||
|
||||
$response = false;
|
||||
|
||||
try{
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/clients/', $data);
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($message);
|
||||
}
|
||||
|
||||
$response->assertStatus(302);
|
||||
|
||||
}
|
||||
|
||||
public function testClientIllegalLanguage()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'name' => $this->faker->firstName,
|
||||
'id_number' => 'Coolio',
|
||||
'settings' => [
|
||||
'currency_id' => '1',
|
||||
'language_id' => 'a',
|
||||
'payment_terms' => '1',
|
||||
'valid_until' => '1',
|
||||
'default_task_rate' => 10,
|
||||
'send_reminders' => true
|
||||
]
|
||||
];
|
||||
|
||||
$response = false;
|
||||
|
||||
try{
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/clients/', $data);
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($message);
|
||||
}
|
||||
|
||||
$response->assertStatus(302);
|
||||
|
||||
}
|
||||
|
||||
public function testClientIllegalPaymenTerms()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'name' => $this->faker->firstName,
|
||||
'id_number' => 'Coolio',
|
||||
'settings' => [
|
||||
'currency_id' => '1',
|
||||
'language_id' => '1',
|
||||
'payment_terms' => 'a',
|
||||
'valid_until' => '1',
|
||||
'default_task_rate' => 10,
|
||||
'send_reminders' => true
|
||||
]
|
||||
];
|
||||
|
||||
$response = false;
|
||||
|
||||
try{
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/clients/', $data);
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($message);
|
||||
}
|
||||
|
||||
$response->assertStatus(302);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testClientIllegalValidUntil()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'name' => $this->faker->firstName,
|
||||
'id_number' => 'Coolio',
|
||||
'settings' => [
|
||||
'currency_id' => '1',
|
||||
'language_id' => '1',
|
||||
'payment_terms' => '1',
|
||||
'valid_until' => 'a',
|
||||
'default_task_rate' => 10,
|
||||
'send_reminders' => true
|
||||
]
|
||||
];
|
||||
|
||||
$response = false;
|
||||
|
||||
try{
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/clients/', $data);
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($message);
|
||||
}
|
||||
|
||||
$response->assertStatus(302);
|
||||
|
||||
}
|
||||
|
||||
public function testClientIllegalDefaultTaskRate()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'name' => $this->faker->firstName,
|
||||
'id_number' => 'Coolio',
|
||||
'settings' => [
|
||||
'currency_id' => '1',
|
||||
'language_id' => '1',
|
||||
'payment_terms' => '1',
|
||||
'valid_until' => '1',
|
||||
'default_task_rate' => "a",
|
||||
'send_reminders' => true
|
||||
]
|
||||
];
|
||||
|
||||
$response = false;
|
||||
|
||||
try{
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/clients/', $data);
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($message);
|
||||
}
|
||||
|
||||
$response->assertStatus(200);
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertFalse(array_key_exists('default_task_rate', $arr));
|
||||
|
||||
}
|
||||
|
||||
public function testClientIllegalSendReminderBool()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'name' => $this->faker->firstName,
|
||||
'id_number' => 'Coolio',
|
||||
'settings' => [
|
||||
'currency_id' => '1',
|
||||
'language_id' => '1',
|
||||
'payment_terms' => '1',
|
||||
'valid_until' => '1',
|
||||
'default_task_rate' => "a",
|
||||
'send_reminders' => "faaalse"
|
||||
]
|
||||
];
|
||||
|
||||
$response = false;
|
||||
|
||||
try{
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/clients/', $data);
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($message);
|
||||
}
|
||||
|
||||
$response->assertStatus(302);
|
||||
|
||||
}
|
||||
|
||||
public function testClientSettingBools()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'name' => $this->faker->firstName,
|
||||
'id_number' => 'Coolio',
|
||||
'settings' => [
|
||||
'currency_id' => '1',
|
||||
'language_id' => '1',
|
||||
'payment_terms' => '1',
|
||||
'valid_until' => '1',
|
||||
'default_task_rate' => "a",
|
||||
'send_reminders' => "true"
|
||||
]
|
||||
];
|
||||
|
||||
$response = false;
|
||||
|
||||
try{
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->post('/api/v1/clients/', $data);
|
||||
} catch (ValidationException $e) {
|
||||
$message = json_decode($e->validator->getMessageBag(), 1);
|
||||
nlog($message);
|
||||
}
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user