mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Merge branch 'v5-develop' into yodlee
This commit is contained in:
commit
ce47e30aa2
@ -1 +1 @@
|
||||
5.5.27
|
||||
5.5.32
|
@ -712,14 +712,23 @@ class CheckData extends Command
|
||||
->pluck('p')
|
||||
->first();
|
||||
|
||||
$over_payment = $over_payment*-1;
|
||||
|
||||
if(floatval($over_payment) == floatval($client->balance)){
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
$this->logMessage("# {$client->id} # {$client->name} {$client->balance} is invalid should be {$over_payment}");
|
||||
|
||||
}
|
||||
|
||||
$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 = $over_payment * -1;
|
||||
$client->balance = $over_payment;
|
||||
$client->save();
|
||||
|
||||
}
|
||||
|
@ -437,7 +437,7 @@ class CreateTestData extends Command
|
||||
'company_id' => $client->company->id,
|
||||
]);
|
||||
|
||||
Document::factory()->count(50)->create([
|
||||
Document::factory()->count(5)->create([
|
||||
'user_id' => $client->user->id,
|
||||
'company_id' => $client->company_id,
|
||||
'documentable_type' => Vendor::class,
|
||||
|
@ -25,9 +25,9 @@ class RecurringInvoiceToInvoiceFactory
|
||||
$invoice->discount = $recurring_invoice->discount;
|
||||
$invoice->is_amount_discount = $recurring_invoice->is_amount_discount;
|
||||
$invoice->po_number = $recurring_invoice->po_number;
|
||||
$invoice->footer = self::tranformObject($recurring_invoice->footer, $client);
|
||||
$invoice->terms = self::tranformObject($recurring_invoice->terms, $client);
|
||||
$invoice->public_notes = self::tranformObject($recurring_invoice->public_notes, $client);
|
||||
$invoice->footer = $recurring_invoice->footer ? self::tranformObject($recurring_invoice->footer, $client) : null;
|
||||
$invoice->terms = $recurring_invoice->terms ? self::tranformObject($recurring_invoice->terms, $client) : null;
|
||||
$invoice->public_notes = $recurring_invoice->public_notes ? self::tranformObject($recurring_invoice->public_notes, $client) : null;
|
||||
$invoice->private_notes = $recurring_invoice->private_notes;
|
||||
$invoice->is_deleted = $recurring_invoice->is_deleted;
|
||||
$invoice->line_items = self::transformItems($recurring_invoice, $client);
|
||||
|
@ -28,7 +28,7 @@ class ClientFilters extends QueryFilters
|
||||
* @param string $name
|
||||
* @return Builder
|
||||
*/
|
||||
public function name(string $name): Builder
|
||||
public function name(string $name = ''): Builder
|
||||
{
|
||||
if(strlen($name) >=1)
|
||||
return $this->builder->where('name', 'like', '%'.$name.'%');
|
||||
|
@ -640,7 +640,14 @@ class ClientController extends BaseController
|
||||
{
|
||||
//delete all documents
|
||||
$client->documents->each(function ($document) {
|
||||
Storage::disk(config('filesystems.default'))->delete($document->url);
|
||||
|
||||
try{
|
||||
Storage::disk(config('filesystems.default'))->delete($document->url);
|
||||
}
|
||||
catch(\Exception $e){
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
//force delete the client
|
||||
|
@ -102,7 +102,7 @@ class SelfUpdateController extends BaseController
|
||||
nlog('Extracting zip');
|
||||
|
||||
//clean up old snappdf installations
|
||||
$this->cleanOldSnapChromeBinaries();
|
||||
//$this->cleanOldSnapChromeBinaries();
|
||||
|
||||
$zipFile = new \PhpZip\ZipFile();
|
||||
|
||||
|
@ -36,6 +36,7 @@ class SubdomainController extends BaseController
|
||||
'lb',
|
||||
'shopify',
|
||||
'beta',
|
||||
'prometh'
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
|
@ -36,6 +36,10 @@ class ExpenseMap
|
||||
17 => 'expense.tax_rate3',
|
||||
18 => 'expense.uses_inclusive_taxes',
|
||||
19 => 'expense.payment_date',
|
||||
20 => 'expense.custom_value1',
|
||||
21 => 'expense.custom_value2',
|
||||
22 => 'expense.custom_value3',
|
||||
23 => 'expense.custom_value4',
|
||||
|
||||
];
|
||||
}
|
||||
@ -63,6 +67,10 @@ class ExpenseMap
|
||||
17 => 'texts.tax_rate3',
|
||||
18 => 'texts.uses_inclusive_taxes',
|
||||
19 => 'texts.payment_date',
|
||||
20 => 'texts.custom_value1',
|
||||
21 => 'texts.custom_value2',
|
||||
22 => 'texts.custom_value3',
|
||||
23 => 'texts.custom_value4',
|
||||
|
||||
];
|
||||
}
|
||||
|
@ -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', 'aol.com', 'mail.ru'])){
|
||||
if(in_array($this->getDomain($this->request['email']), ['yopmail.com','gmail.com', 'hotmail.com', 'outlook.com', 'yahoo.com', 'aol.com', 'mail.ru'])){
|
||||
$sp794f3f->account_sms_verified = false;
|
||||
}
|
||||
|
||||
|
@ -515,8 +515,8 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
$path = 'backups';
|
||||
|
||||
if(!Storage::disk(config('filesystems.default'))->exists($path))
|
||||
Storage::disk(config('filesystems.default'))->makeDirectory($path, 0775);
|
||||
// if(!Storage::disk(config('filesystems.default'))->exists($path))
|
||||
// Storage::disk(config('filesystems.default'))->makeDirectory($path, 0775);
|
||||
|
||||
$zip_path = public_path('storage/backups/'.$file_name);
|
||||
$zip = new \ZipArchive();
|
||||
|
@ -213,11 +213,7 @@ class CreateEntityPdf implements ShouldQueue
|
||||
|
||||
if ($pdf) {
|
||||
try {
|
||||
if (! Storage::disk($this->disk)->exists($path)) {
|
||||
Storage::disk($this->disk)->makeDirectory($path, 0775);
|
||||
}
|
||||
|
||||
Storage::disk($this->disk)->put($file_path, $pdf, 'public');
|
||||
Storage::disk($this->disk)->put($file_path, $pdf);
|
||||
} catch (\Exception $e) {
|
||||
throw new FilePermissionsFailure($e->getMessage());
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ class ClientLedgerBalanceUpdate implements ShouldQueue
|
||||
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
CompanyLedger::where('balance', 0)->where('client_id', $this->client->id)->cursor()->each(function ($company_ledger) {
|
||||
CompanyLedger::where('balance', 0)->where('client_id', $this->client->id)->orderBy('updated_at', 'ASC')->cursor()->each(function ($company_ledger) {
|
||||
if ($company_ledger->balance > 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -83,9 +83,23 @@ class UpdateOrCreateProduct implements ShouldQueue
|
||||
|
||||
$product = Product::withTrashed()->firstOrNew(['product_key' => $item->product_key, 'company_id' => $this->invoice->company->id]);
|
||||
|
||||
/* If a user is using placeholders in their descriptions, do not update the products */
|
||||
$string_hit = false;
|
||||
|
||||
foreach ( [':MONTH',':YEAR',':QUARTER',':WEEK'] as $string )
|
||||
{
|
||||
|
||||
if(stripos($product->notes, $string) !== FALSE) {
|
||||
$string_hit = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if($string_hit)
|
||||
continue;
|
||||
|
||||
$product->product_key = $item->product_key;
|
||||
$product->notes = isset($item->notes) ? $item->notes : '';
|
||||
//$product->cost = isset($item->cost) ? $item->cost : 0; //this value shouldn't be updated.
|
||||
$product->price = isset($item->cost) ? $item->cost : 0;
|
||||
|
||||
if (! $product->id) {
|
||||
|
@ -167,7 +167,7 @@ class UploadFile implements ShouldQueue
|
||||
$previewHeight = $height * Document::DOCUMENT_PREVIEW_SIZE / $width;
|
||||
} else {
|
||||
$previewHeight = Document::DOCUMENT_PREVIEW_SIZE;
|
||||
$previewWidth = $width * DOCUMENT_PREVIEW_SIZE / $height;
|
||||
$previewWidth = $width * Document::DOCUMENT_PREVIEW_SIZE / $height;
|
||||
}
|
||||
|
||||
$img->resize($previewWidth, $previewHeight);
|
||||
|
10
app/Jobs/Vendor/CreatePurchaseOrderPdf.php
vendored
10
app/Jobs/Vendor/CreatePurchaseOrderPdf.php
vendored
@ -99,18 +99,11 @@ class CreatePurchaseOrderPdf implements ShouldQueue
|
||||
if ($pdf) {
|
||||
|
||||
try{
|
||||
|
||||
if(!Storage::disk($this->disk)->exists($this->path))
|
||||
Storage::disk($this->disk)->makeDirectory($this->path, 0775);
|
||||
|
||||
Storage::disk($this->disk)->put($this->file_path, $pdf, 'public');
|
||||
|
||||
Storage::disk($this->disk)->put($this->file_path, $pdf);
|
||||
}
|
||||
catch(\Exception $e)
|
||||
{
|
||||
|
||||
throw new FilePermissionsFailure($e->getMessage());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,7 +202,6 @@ class CreatePurchaseOrderPdf implements ShouldQueue
|
||||
|
||||
if($numbered_pdf)
|
||||
$pdf = $numbered_pdf;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -146,10 +146,17 @@ class TemplateEmail extends Mailable
|
||||
|
||||
}
|
||||
|
||||
//22-10-2022 - Performance - To improve the performance/reliability of sending emails, attaching as Data is much better, stubs in place
|
||||
foreach ($this->build_email->getAttachments() as $file) {
|
||||
if (is_string($file)) {
|
||||
// nlog($file);
|
||||
// $file_data = file_get_contents($file);
|
||||
// $this->attachData($file_data, basename($file));
|
||||
$this->attach($file);
|
||||
} elseif (is_array($file)) {
|
||||
// nlog($file['path']);
|
||||
// $file_data = file_get_contents($file['path']);
|
||||
// $this->attachData($file_data, $file['name']);
|
||||
$this->attach($file['path'], ['as' => $file['name'], 'mime' => null]);
|
||||
}
|
||||
}
|
||||
|
@ -36,15 +36,15 @@ class Backup extends BaseModel
|
||||
$filename = now()->format('Y_m_d').'_'.md5(time()).'.html';
|
||||
$file_path = $path.$filename;
|
||||
|
||||
Storage::disk(config('filesystems.default'))->makeDirectory($path, 0775);
|
||||
// Storage::disk(config('filesystems.default'))->makeDirectory($path, 0775);
|
||||
|
||||
Storage::disk(config('filesystems.default'))->put($file_path, $html);
|
||||
|
||||
if (Storage::disk(config('filesystems.default'))->exists($file_path)) {
|
||||
// if (Storage::disk(config('filesystems.default'))->exists($file_path)) {
|
||||
$this->html_backup = '';
|
||||
$this->filename = $file_path;
|
||||
$this->save();
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
public function deleteFile()
|
||||
|
@ -17,6 +17,7 @@ use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Mail\ClientContact\ClientContactResetPasswordObject;
|
||||
use App\Models\Presenters\ClientContactPresenter;
|
||||
use App\Notifications\ClientContactResetPassword;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Contracts\Translation\HasLocalePreference;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@ -257,8 +258,33 @@ class ClientContact extends Authenticatable implements HasLocalePreference
|
||||
*/
|
||||
public function getLoginLink()
|
||||
{
|
||||
$domain = isset($this->company->portal_domain) ? $this->company->portal_domain : $this->company->domain();
|
||||
// $domain = isset($this->company->portal_domain) ? $this->company->portal_domain : $this->company->domain();
|
||||
|
||||
// return $domain.'/client/key_login/'.$this->contact_key;
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
$domain = $this->company->domain();
|
||||
} else {
|
||||
$domain = config('ninja.app_url');
|
||||
}
|
||||
|
||||
switch ($this->company->portal_mode) {
|
||||
case 'subdomain':
|
||||
return $domain.'/client/key_login/'.$this->contact_key;
|
||||
break;
|
||||
case 'iframe':
|
||||
return $domain.'/client/key_login/'.$this->contact_key;
|
||||
//return $domain . $entity_type .'/'. $this->contact->client->client_hash .'/'. $this->key;
|
||||
break;
|
||||
case 'domain':
|
||||
return $domain.'/client/key_login/'.$this->contact_key;
|
||||
break;
|
||||
|
||||
default:
|
||||
return '';
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return $domain.'/client/key_login/'.$this->contact_key;
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ class CompanyGateway extends BaseModel
|
||||
'require_postal_code',
|
||||
'require_client_phone',
|
||||
'require_contact_name',
|
||||
'require_contact_email',
|
||||
'update_details',
|
||||
'config',
|
||||
'fees_and_limits',
|
||||
|
@ -288,11 +288,21 @@ class Credit extends BaseModel
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
|
||||
try {
|
||||
$file_exists = Storage::disk(config('filesystems.default'))->exists($file_path);
|
||||
} catch (\Exception $e) {
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
|
||||
if ($file_exists) {
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
|
||||
|
||||
if (Storage::disk('public')->exists($file_path)) {
|
||||
return Storage::disk('public')->{$type}($file_path);
|
||||
}
|
||||
|
||||
|
||||
$file_path = (new CreateEntityPdf($invitation))->handle();
|
||||
|
||||
return Storage::disk('public')->{$type}($file_path);
|
||||
|
@ -451,6 +451,17 @@ class Invoice extends BaseModel
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
|
||||
try {
|
||||
$file_exists = Storage::disk(config('filesystems.default'))->exists($file_path);
|
||||
} catch (\Exception $e) {
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
|
||||
if ($file_exists) {
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
$file_exists = Storage::disk('public')->exists($file_path);
|
||||
} catch (\Exception $e) {
|
||||
|
@ -247,6 +247,16 @@ class Quote extends BaseModel
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
|
||||
try {
|
||||
$file_exists = Storage::disk(config('filesystems.default'))->exists($file_path);
|
||||
} catch (\Exception $e) {
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
|
||||
if ($file_exists) {
|
||||
return Storage::disk(config('filesystems.default'))->{$type}($file_path);
|
||||
}
|
||||
|
||||
if (Storage::disk('public')->exists($file_path)) {
|
||||
return Storage::disk('public')->{$type}($file_path);
|
||||
}
|
||||
|
@ -41,6 +41,8 @@ class SystemLog extends Model
|
||||
|
||||
const CATEGORY_SECURITY = 5;
|
||||
|
||||
const CATEGORY_LOG = 6;
|
||||
|
||||
/* Event IDs*/
|
||||
const EVENT_PAYMENT_RECONCILIATION_FAILURE = 10;
|
||||
|
||||
@ -127,6 +129,8 @@ class SystemLog extends Model
|
||||
|
||||
const TYPE_LOGIN_FAILURE = 801;
|
||||
|
||||
const TYPE_GENERIC = 900;
|
||||
|
||||
protected $fillable = [
|
||||
'client_id',
|
||||
'company_id',
|
||||
|
86
app/Notifications/Ninja/DomainRenewalFailureNotification.php
Normal file
86
app/Notifications/Ninja/DomainRenewalFailureNotification.php
Normal 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 DomainRenewalFailureNotification 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 _renewal_ failure:\n";
|
||||
$content .= "{$this->domain}\n";
|
||||
|
||||
return (new SlackMessage)
|
||||
->success()
|
||||
->from(ctrans('texts.notification_bot'))
|
||||
->image('https://app.invoiceninja.com/favicon.png')
|
||||
->content($content);
|
||||
}
|
||||
}
|
@ -602,11 +602,11 @@ class BaseDriver extends AbstractPaymentDriver
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->company_gateway->require_contact_email) {
|
||||
if ($this->checkRequiredResource($this->email)) {
|
||||
$this->required_fields[] = 'contact_email';
|
||||
}
|
||||
}
|
||||
// if ($this->company_gateway->require_contact_email) {
|
||||
// if ($this->checkRequiredResource($this->email)) {
|
||||
// $this->required_fields[] = 'contact_email';
|
||||
// }
|
||||
// }
|
||||
|
||||
// if ($this->company_gateway->require_contact_name) {
|
||||
// if ($this->checkRequiredResource($this->first_name)) {
|
||||
|
@ -14,9 +14,11 @@ namespace App\PaymentDrivers\CheckoutCom;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Http\Requests\ClientPortal\Payments\PaymentResponseRequest;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\CheckoutComPaymentDriver;
|
||||
use App\PaymentDrivers\Common\MethodInterface;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
@ -245,6 +247,16 @@ class CreditCard implements MethodInterface
|
||||
if ($response['status'] == 'Declined') {
|
||||
$this->checkout->unWindGatewayFees($this->checkout->payment_hash);
|
||||
|
||||
//18-10-2022
|
||||
SystemLogger::dispatch(
|
||||
$response,
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_ERROR,
|
||||
SystemLog::TYPE_CHECKOUT,
|
||||
$this->checkout->client,
|
||||
$this->checkout->client->company,
|
||||
);
|
||||
|
||||
return $this->processUnsuccessfulPayment($response);
|
||||
}
|
||||
} catch (CheckoutApiException $e) {
|
||||
|
@ -78,6 +78,7 @@ class Charge
|
||||
'payment_method' => $cgt->token,
|
||||
'customer' => $cgt->gateway_customer_reference,
|
||||
'confirm' => true,
|
||||
// 'off_session' => true,
|
||||
'description' => $description,
|
||||
'metadata' => [
|
||||
'payment_hash' => $payment_hash->hash,
|
||||
|
@ -398,7 +398,7 @@ class StripePaymentDriver extends BaseDriver
|
||||
{
|
||||
$this->init();
|
||||
|
||||
$params = [];
|
||||
$params = ['usage' => 'off_session'];
|
||||
$meta = $this->stripe_connect_auth;
|
||||
|
||||
return SetupIntent::create($params, $meta);
|
||||
|
@ -49,8 +49,8 @@ class EntityPolicy
|
||||
return ($user->isAdmin() && $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);
|
||||
|| ($user->owns($entity) && $entity->company_id == $user->companyId())
|
||||
|| ($user->assigned($entity) && $entity->company_id == $user->companyId());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -66,7 +66,7 @@ class EntityPolicy
|
||||
return ($user->isAdmin() && $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);
|
||||
|| ($user->owns($entity) && $entity->company_id == $user->companyId())
|
||||
|| ($user->assigned($entity) && $entity->company_id == $user->companyId());
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,8 @@ class ActivityRepository extends BaseRepository
|
||||
|
||||
$activity->save();
|
||||
|
||||
$this->createBackup($entity, $activity);
|
||||
//rate limiter
|
||||
// $this->createBackup($entity, $activity);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,7 +83,8 @@ class ActivityRepository extends BaseRepository
|
||||
$backup = new Backup();
|
||||
$entity->load('client');
|
||||
$contact = $entity->client->primary_contact()->first();
|
||||
$backup->html_backup = $this->generateHtml($entity);
|
||||
$backup->html_backup = '';
|
||||
// $backup->html_backup = $this->generateHtml($entity);
|
||||
$backup->amount = $entity->amount;
|
||||
$backup->activity_id = $activity->id;
|
||||
$backup->json_backup = '';
|
||||
|
@ -19,9 +19,11 @@ use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\Helpers;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Utils\Traits\SavesDocuments;
|
||||
use Google\Service\Vision\Property;
|
||||
use ReflectionClass;
|
||||
|
||||
class BaseRepository
|
||||
@ -108,32 +110,6 @@ class BaseRepository
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $ids
|
||||
* @param $action
|
||||
*
|
||||
* @return int
|
||||
* @deprecated - this doesn't appear to be used anywhere?
|
||||
*/
|
||||
// public function bulk($ids, $action)
|
||||
// {
|
||||
// if (! $ids) {
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
// $ids = $this->transformKeys($ids);
|
||||
|
||||
// $entities = $this->findByPublicIdsWithTrashed($ids);
|
||||
|
||||
// foreach ($entities as $entity) {
|
||||
// if (auth()->user()->can('edit', $entity)) {
|
||||
// $this->$action($entity);
|
||||
// }
|
||||
// }
|
||||
|
||||
// return count($entities);
|
||||
// }
|
||||
|
||||
/* Returns an invoice if defined as a key in the $resource array*/
|
||||
public function getInvitation($invitation, $resource)
|
||||
{
|
||||
@ -171,7 +147,7 @@ class BaseRepository
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
protected function alternativeSave($data, $model)
|
||||
{
|
||||
{ //$start = microtime(true);
|
||||
//forces the client_id if it doesn't exist
|
||||
if(array_key_exists('client_id', $data))
|
||||
$model->client_id = $data['client_id'];
|
||||
@ -208,9 +184,21 @@ class BaseRepository
|
||||
$model->custom_surcharge_tax3 = $client->company->custom_surcharge_taxes3;
|
||||
$model->custom_surcharge_tax4 = $client->company->custom_surcharge_taxes4;
|
||||
|
||||
if(!$model->id)
|
||||
if(!$model->id){
|
||||
$this->new_model = true;
|
||||
|
||||
|
||||
if(is_array($model->line_items))
|
||||
{
|
||||
$model->line_items = (collect($model->line_items))->map(function ($item) use($model,$client) {
|
||||
|
||||
$item->notes = Helpers::processReservedKeywords($item->notes, $client);
|
||||
|
||||
return $item;
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$model->saveQuietly();
|
||||
|
||||
/* Model now persisted, now lets do some child tasks */
|
||||
@ -378,6 +366,8 @@ class BaseRepository
|
||||
|
||||
$model->save();
|
||||
|
||||
// nlog("save time = ". microtime(true) - $start);
|
||||
|
||||
return $model->fresh();
|
||||
}
|
||||
}
|
||||
|
@ -105,10 +105,7 @@ class GenerateDeliveryNote
|
||||
info($maker->getCompiledHTML());
|
||||
}
|
||||
|
||||
if (! Storage::disk($this->disk)->exists($this->invoice->client->invoice_filepath($invitation))) {
|
||||
Storage::disk($this->disk)->makeDirectory($this->invoice->client->invoice_filepath($invitation), 0775);
|
||||
}
|
||||
Storage::disk($this->disk)->put($file_path, $pdf, 'public');
|
||||
Storage::disk($this->disk)->put($file_path, $pdf);
|
||||
|
||||
return $file_path;
|
||||
}
|
||||
|
@ -218,6 +218,7 @@ class InvoiceService
|
||||
public function markDeleted()
|
||||
{
|
||||
$this->removeUnpaidGatewayFees();
|
||||
$this->deletePdf();
|
||||
|
||||
$this->invoice = (new MarkInvoiceDeleted($this->invoice))->run();
|
||||
|
||||
@ -268,7 +269,11 @@ class InvoiceService
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->invoice->due_date = Carbon::parse($this->invoice->date)->addDays($this->invoice->client->getSetting('payment_terms'));
|
||||
//12-10-2022
|
||||
if($this->invoice->partial > 0 && !$this->invoice->partial_due_date)
|
||||
$this->invoice->partial_due_date = Carbon::parse($this->invoice->date)->addDays($this->invoice->client->getSetting('payment_terms'));
|
||||
else
|
||||
$this->invoice->due_date = Carbon::parse($this->invoice->date)->addDays($this->invoice->client->getSetting('payment_terms'));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -294,6 +299,9 @@ class InvoiceService
|
||||
} elseif ($this->invoice->balance > 0 && $this->invoice->balance < $this->invoice->amount) {
|
||||
$this->setStatus(Invoice::STATUS_PARTIAL);
|
||||
}
|
||||
elseif($this->invoice->balance < 0) {
|
||||
$this->setStatus(Invoice::STATUS_PARTIAL);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -309,7 +317,7 @@ class InvoiceService
|
||||
} elseif ($this->invoice->balance > 0 && $this->invoice->balance < $this->invoice->amount) {
|
||||
$this->invoice->status_id = Invoice::STATUS_PARTIAL;
|
||||
}
|
||||
elseif ($this->invoice->balance < 0) {
|
||||
elseif ($this->invoice->balance > 0) {
|
||||
$this->invoice->status_id = Invoice::STATUS_SENT;
|
||||
}
|
||||
|
||||
|
@ -71,10 +71,6 @@ class MarkInvoiceDeleted extends AbstractService
|
||||
|
||||
private function adjustPaidToDateAndBalance()
|
||||
{
|
||||
// $client = $this->invoice->client->fresh();
|
||||
// $client->paid_to_date += $this->adjustment_amount * -1;
|
||||
// $client->balance += $this->balance_adjustment * -1;
|
||||
// $client->save();
|
||||
|
||||
// 06-09-2022
|
||||
$this->invoice
|
||||
|
@ -28,6 +28,7 @@ class UpdateReminder extends AbstractService
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/* We only support setting reminders based on the due date, not the partial due date */
|
||||
public function run()
|
||||
{
|
||||
if (! $this->settings) {
|
||||
|
@ -16,10 +16,12 @@ use App\Factory\CreditFactory;
|
||||
use App\Factory\InvoiceItemFactory;
|
||||
use App\Jobs\Ninja\TransactionLog;
|
||||
use App\Jobs\Payment\EmailRefundPayment;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\Activity;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Models\SystemLog;
|
||||
use App\Models\TransactionEvent;
|
||||
use App\Repositories\ActivityRepository;
|
||||
use App\Utils\Ninja;
|
||||
@ -77,6 +79,11 @@ class RefundPayment
|
||||
|
||||
TransactionLog::dispatch(TransactionEvent::PAYMENT_REFUND, $transaction, $this->payment->company->db);
|
||||
|
||||
$notes = ctrans('texts.refunded') . " : {$this->total_refund} - " . ctrans('texts.gateway_refund') . " : ";
|
||||
$notes .= $this->refund_data['gateway_refund'] !== false ? ctrans('texts.yes') : ctrans('texts.no');
|
||||
|
||||
$this->createActivity($notes);
|
||||
|
||||
return $this->payment;
|
||||
}
|
||||
|
||||
@ -94,8 +101,6 @@ class RefundPayment
|
||||
|
||||
$this->payment->refunded += $this->total_refund;
|
||||
|
||||
$this->createActivity($this->payment);
|
||||
|
||||
if ($response['success'] == false) {
|
||||
$this->payment->save();
|
||||
|
||||
@ -129,7 +134,7 @@ class RefundPayment
|
||||
$fields->company_id = $this->payment->company_id;
|
||||
$fields->activity_type_id = Activity::REFUNDED_PAYMENT;
|
||||
// $fields->credit_id = $this->credit_note->id; // TODO
|
||||
$fields->notes = json_encode($notes);
|
||||
$fields->notes = $notes;
|
||||
|
||||
if (isset($this->refund_data['invoices'])) {
|
||||
foreach ($this->refund_data['invoices'] as $invoice) {
|
||||
|
@ -128,7 +128,7 @@ class Helpers
|
||||
|
||||
if(!$string_hit)
|
||||
return $value;
|
||||
// 04-10-2022 Return Early if no reserved keywords are present, this is a very expenseive process
|
||||
// 04-10-2022 Return Early if no reserved keywords are present, this is a very expensive process
|
||||
|
||||
Carbon::setLocale($entity->locale());
|
||||
|
||||
@ -296,8 +296,7 @@ class Helpers
|
||||
}
|
||||
|
||||
return $value;
|
||||
// $x = str_replace(["\n", "<br>"], ["\r", "<br>"], $value);
|
||||
// return $x;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -150,7 +150,7 @@ class HtmlEngine
|
||||
$data['$number'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')];
|
||||
$data['$invoice'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number')];
|
||||
$data['$number_short'] = ['value' => $this->entity->number ?: ' ', 'label' => ctrans('texts.invoice_number_short')];
|
||||
$data['$entity.terms'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->terms), $this->client) ?: '', 'label' => ctrans('texts.invoice_terms')];
|
||||
$data['$entity.terms'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->terms ?: ''), $this->client) ?: '', 'label' => ctrans('texts.invoice_terms')];
|
||||
$data['$terms'] = &$data['$entity.terms'];
|
||||
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_invoice').'</a>', 'label' => ctrans('texts.view_invoice')];
|
||||
$data['$viewLink'] = &$data['$view_link'];
|
||||
@ -185,7 +185,7 @@ class HtmlEngine
|
||||
$data['$entity'] = ['value' => '', 'label' => ctrans('texts.quote')];
|
||||
$data['$number'] = ['value' => $this->entity->number ?: '', 'label' => ctrans('texts.quote_number')];
|
||||
$data['$number_short'] = ['value' => $this->entity->number ?: '', 'label' => ctrans('texts.quote_number_short')];
|
||||
$data['$entity.terms'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->terms), $this->client) ?: '', 'label' => ctrans('texts.quote_terms')];
|
||||
$data['$entity.terms'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->terms ?: ''), $this->client) ?: '', 'label' => ctrans('texts.quote_terms')];
|
||||
$data['$terms'] = &$data['$entity.terms'];
|
||||
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_quote').'</a>', 'label' => ctrans('texts.view_quote')];
|
||||
$data['$viewLink'] = &$data['$view_link'];
|
||||
@ -210,7 +210,7 @@ class HtmlEngine
|
||||
$data['$entity'] = ['value' => '', 'label' => ctrans('texts.credit')];
|
||||
$data['$number'] = ['value' => $this->entity->number ?: '', 'label' => ctrans('texts.credit_number')];
|
||||
$data['$number_short'] = ['value' => $this->entity->number ?: '', 'label' => ctrans('texts.credit_number_short')];
|
||||
$data['$entity.terms'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->terms), $this->client) ?: '', 'label' => ctrans('texts.credit_terms')];
|
||||
$data['$entity.terms'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->terms ?: ''), $this->client) ?: '', 'label' => ctrans('texts.credit_terms')];
|
||||
$data['$terms'] = &$data['$entity.terms'];
|
||||
$data['$view_link'] = ['value' => '<a class="button" href="'.$this->invitation->getLink().'">'.ctrans('texts.view_credit').'</a>', 'label' => ctrans('texts.view_credit')];
|
||||
$data['$viewButton'] = &$data['$view_link'];
|
||||
@ -303,7 +303,7 @@ class HtmlEngine
|
||||
$data['$invoice.custom2'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice2', $this->entity->custom_value2, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice2')];
|
||||
$data['$invoice.custom3'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice3', $this->entity->custom_value3, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice3')];
|
||||
$data['$invoice.custom4'] = ['value' => $this->helpers->formatCustomFieldValue($this->company->custom_fields, 'invoice4', $this->entity->custom_value4, $this->client) ?: ' ', 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'invoice4')];
|
||||
$data['$invoice.public_notes'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->public_notes), $this->client) ?: '', 'label' => ctrans('texts.public_notes')];
|
||||
$data['$invoice.public_notes'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->public_notes ?: ''), $this->client) ?: '', 'label' => ctrans('texts.public_notes')];
|
||||
$data['$entity.public_notes'] = &$data['$invoice.public_notes'];
|
||||
$data['$public_notes'] = &$data['$invoice.public_notes'];
|
||||
$data['$notes'] = &$data['$public_notes'];
|
||||
@ -535,7 +535,7 @@ class HtmlEngine
|
||||
$data['$description'] = ['value' => '', 'label' => ctrans('texts.description')];
|
||||
|
||||
//$data['$entity_footer'] = ['value' => $this->client->getSetting("{$this->entity_string}_footer"), 'label' => ''];
|
||||
$data['$entity_footer'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->footer), $this->client), 'label' => ''];
|
||||
$data['$entity_footer'] = ['value' => Helpers::processReservedKeywords(\nl2br($this->entity->footer ?: ''), $this->client), 'label' => ''];
|
||||
$data['$footer'] = &$data['$entity_footer'];
|
||||
|
||||
$data['$page_size'] = ['value' => $this->settings->page_size, 'label' => ''];
|
||||
|
@ -54,17 +54,33 @@ class Number
|
||||
* Formats a given value based on the clients currency.
|
||||
*
|
||||
* @param float $value The number to be formatted
|
||||
* @param object $currency The client currency object
|
||||
*
|
||||
* @return string The formatted value
|
||||
*/
|
||||
public static function formatValueNoTrailingZeroes($value, $currency) :string
|
||||
public static function formatValueNoTrailingZeroes($value, $entity) :string
|
||||
{
|
||||
$value = floatval($value);
|
||||
|
||||
$currency = $entity->currency();
|
||||
|
||||
$thousand = $currency->thousand_separator;
|
||||
$decimal = $currency->decimal_separator;
|
||||
$precision = $currency->precision;
|
||||
// $precision = $currency->precision;
|
||||
|
||||
if ($entity instanceof Company) {
|
||||
$country = $entity->country();
|
||||
} else {
|
||||
$country = $entity->country;
|
||||
}
|
||||
|
||||
/* Country settings override client settings */
|
||||
if (isset($country->thousand_separator) && strlen($country->thousand_separator) >= 1) {
|
||||
$thousand = $country->thousand_separator;
|
||||
}
|
||||
|
||||
if (isset($country->decimal_separator) && strlen($country->decimal_separator) >= 1) {
|
||||
$decimal = $country->decimal_separator;
|
||||
}
|
||||
|
||||
$precision = 10;
|
||||
|
||||
|
@ -65,7 +65,17 @@ trait ClientGroupSettingsSaver
|
||||
}
|
||||
|
||||
$entity->settings = $entity_settings;
|
||||
$entity->save();
|
||||
|
||||
try{
|
||||
$entity->save();
|
||||
}
|
||||
catch(\Exception $e){
|
||||
|
||||
nlog("client settings failure");
|
||||
nlog($entity_settings);
|
||||
nlog($e->getMessage());
|
||||
|
||||
}
|
||||
|
||||
return $entity_settings;
|
||||
}
|
||||
|
@ -300,13 +300,13 @@ trait MakesInvoiceValues
|
||||
$data[$key][$table_type.'.notes'] = Helpers::processReservedKeywords($item->notes, $entity);
|
||||
$data[$key][$table_type.'.description'] = Helpers::processReservedKeywords($item->notes, $entity);
|
||||
|
||||
$data[$key][$table_type.".{$_table_type}1"] = strlen($item->custom_value1) > 1 ? $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}1", $item->custom_value1, $entity) : '';
|
||||
$data[$key][$table_type.".{$_table_type}2"] = strlen($item->custom_value2) > 2 ? $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}2", $item->custom_value2, $entity) : '';
|
||||
$data[$key][$table_type.".{$_table_type}3"] = strlen($item->custom_value3) > 3 ? $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}3", $item->custom_value3, $entity) : '';
|
||||
$data[$key][$table_type.".{$_table_type}4"] = strlen($item->custom_value4) > 4 ? $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}4", $item->custom_value4, $entity) : '';
|
||||
$data[$key][$table_type.".{$_table_type}1"] = strlen($item->custom_value1) >= 1 ? $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}1", $item->custom_value1, $entity) : '';
|
||||
$data[$key][$table_type.".{$_table_type}2"] = strlen($item->custom_value2) >= 1 ? $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}2", $item->custom_value2, $entity) : '';
|
||||
$data[$key][$table_type.".{$_table_type}3"] = strlen($item->custom_value3) >= 1 ? $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}3", $item->custom_value3, $entity) : '';
|
||||
$data[$key][$table_type.".{$_table_type}4"] = strlen($item->custom_value4) >= 1 ? $helpers->formatCustomFieldValue($this->company->custom_fields, "{$_table_type}4", $item->custom_value4, $entity) : '';
|
||||
|
||||
if ($item->quantity > 0 || $item->cost > 0) {
|
||||
$data[$key][$table_type.'.quantity'] = Number::formatValueNoTrailingZeroes($item->quantity, $entity_currency);
|
||||
$data[$key][$table_type.'.quantity'] = Number::formatValueNoTrailingZeroes($item->quantity, $entity);
|
||||
|
||||
$data[$key][$table_type.'.unit_cost'] = Number::formatMoneyNoRounding($item->cost, $entity);
|
||||
|
||||
|
@ -79,6 +79,19 @@ return [
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
'r2' => [
|
||||
'driver' => 's3',
|
||||
'key' => env('R2_ACCESS_KEY_ID'),
|
||||
'secret' => env('R2_SECRET_ACCESS_KEY'),
|
||||
'region' => env('R2_DEFAULT_REGION'),
|
||||
'bucket' => env('R2_BUCKET'),
|
||||
'url' => env('R2_URL'),
|
||||
'visibility' => 'private',
|
||||
'endpoint' => env('R2_ENDPOINT'),
|
||||
'use_path_style_endpoint' => env('R2_USE_PATH_STYLE_ENDPOINT', false),
|
||||
'throw' => false,
|
||||
],
|
||||
|
||||
'gcs' => [
|
||||
'driver' => 'gcs',
|
||||
'project_id' => env('GOOGLE_CLOUD_PROJECT_ID', 'your-project-id'),
|
||||
|
@ -40,7 +40,7 @@ return [
|
||||
/*
|
||||
* Route for accessing parsed swagger annotations.
|
||||
*/
|
||||
'docs' => 'docs',
|
||||
'docs' => 'swagger-docs-that-should-be-inaccessible',
|
||||
|
||||
/*
|
||||
* Route for Oauth2 authentication callback.
|
||||
|
@ -43,6 +43,7 @@ return [
|
||||
'password' => env('MAIL_PASSWORD'),
|
||||
'timeout' => null,
|
||||
'local_domain' => env('MAIL_EHLO_DOMAIN'),
|
||||
'verify_peer' => env('MAIL_VERIFY_PEER', true),
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
|
@ -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.5.27',
|
||||
'app_tag' => '5.5.27',
|
||||
'app_version' => '5.5.32',
|
||||
'app_tag' => '5.5.32',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('company_tokens', function (Blueprint $table) {
|
||||
$table->index('token');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
}
|
||||
};
|
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('documents', function (Blueprint $table) {
|
||||
$table->index(['documentable_id', 'documentable_type', 'deleted_at']);
|
||||
});
|
||||
|
||||
Schema::table('expenses', function (Blueprint $table) {
|
||||
$table->index(['invoice_id', 'deleted_at']);
|
||||
});
|
||||
|
||||
Schema::table('company_tokens', function (Blueprint $table) {
|
||||
$table->dropIndex('company_tokens_token_index');
|
||||
$table->index(['token','deleted_at']);
|
||||
});
|
||||
|
||||
Schema::table('invoice_invitations', function (Blueprint $table) {
|
||||
$table->dropIndex('invoice_invitations_key_index');
|
||||
$table->index(['key','deleted_at']);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
@ -3414,7 +3414,7 @@ $LANG = array(
|
||||
'credit_number_counter' => 'Credit Number Counter',
|
||||
'reset_counter_date' => 'Reset Counter Date',
|
||||
'counter_padding' => 'Counter Padding',
|
||||
'shared_invoice_quote_counter' => 'Shared Invoice Quote Counter',
|
||||
'shared_invoice_quote_counter' => 'Share Invoice Quote Counter',
|
||||
'default_tax_name_1' => 'Default Tax Name 1',
|
||||
'default_tax_rate_1' => 'Default Tax Rate 1',
|
||||
'default_tax_name_2' => 'Default Tax Name 2',
|
||||
@ -4199,7 +4199,7 @@ $LANG = array(
|
||||
'client_id_number' => 'Client ID Number',
|
||||
'count_minutes' => ':count Minutes',
|
||||
'password_timeout' => 'Password Timeout',
|
||||
'shared_invoice_credit_counter' => 'Shared Invoice/Credit Counter',
|
||||
'shared_invoice_credit_counter' => 'Share Invoice/Credit Counter',
|
||||
|
||||
'activity_80' => ':user created subscription :subscription',
|
||||
'activity_81' => ':user updated subscription :subscription',
|
||||
@ -4776,7 +4776,8 @@ $LANG = array(
|
||||
'client_email' => 'Client Email',
|
||||
'invoice_task_project' => 'Invoice Task Project',
|
||||
'invoice_task_project_help' => 'Add the project to the invoice line items',
|
||||
|
||||
'bulk_action' => 'Bulk Action',
|
||||
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
136
preload.php
Normal file
136
preload.php
Normal file
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
class Preloader
|
||||
{
|
||||
private array $ignores = [];
|
||||
|
||||
private static int $count = 0;
|
||||
|
||||
private array $paths;
|
||||
|
||||
private array $fileMap;
|
||||
|
||||
public function __construct(string ...$paths)
|
||||
{
|
||||
$this->paths = $paths;
|
||||
|
||||
// We'll use composer's classmap
|
||||
// to easily find which classes to autoload,
|
||||
// based on their filename
|
||||
$classMap = require __DIR__ . '/vendor/composer/autoload_classmap.php';
|
||||
|
||||
$this->fileMap = array_flip($classMap);
|
||||
}
|
||||
|
||||
public function paths(string ...$paths): Preloader
|
||||
{
|
||||
$this->paths = array_merge(
|
||||
$this->paths,
|
||||
$paths
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function ignore(string ...$names): Preloader
|
||||
{
|
||||
$this->ignores = array_merge(
|
||||
$this->ignores,
|
||||
$names
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function load(): void
|
||||
{
|
||||
// We'll loop over all registered paths
|
||||
// and load them one by one
|
||||
foreach ($this->paths as $path) {
|
||||
$this->loadPath(rtrim($path, '/'));
|
||||
}
|
||||
|
||||
$count = self::$count;
|
||||
|
||||
echo "[Preloader] Preloaded {$count} classes" . PHP_EOL;
|
||||
}
|
||||
|
||||
private function loadPath(string $path): void
|
||||
{
|
||||
// If the current path is a directory,
|
||||
// we'll load all files in it
|
||||
if (is_dir($path)) {
|
||||
$this->loadDir($path);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise we'll just load this one file
|
||||
$this->loadFile($path);
|
||||
}
|
||||
|
||||
private function loadDir(string $path): void
|
||||
{
|
||||
$handle = opendir($path);
|
||||
|
||||
// We'll loop over all files and directories
|
||||
// in the current path,
|
||||
// and load them one by one
|
||||
while ($file = readdir($handle)) {
|
||||
if (in_array($file, ['.', '..'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->loadPath("{$path}/{$file}");
|
||||
}
|
||||
|
||||
closedir($handle);
|
||||
}
|
||||
|
||||
private function loadFile(string $path): void
|
||||
{
|
||||
// We resolve the classname from composer's autoload mapping
|
||||
$class = $this->fileMap[$path] ?? null;
|
||||
|
||||
// And use it to make sure the class shouldn't be ignored
|
||||
if ($this->shouldIgnore($class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Finally we require the path,
|
||||
// causing all its dependencies to be loaded as well
|
||||
require_once($path);
|
||||
|
||||
self::$count++;
|
||||
|
||||
echo "[Preloader] Preloaded `{$class}`" . PHP_EOL;
|
||||
}
|
||||
|
||||
private function shouldIgnore(?string $name): bool
|
||||
{
|
||||
if ($name === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($this->ignores as $ignore) {
|
||||
if (strpos($name, $ignore) === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
(new Preloader())
|
||||
->paths(__DIR__ . '/vendor/laravel')
|
||||
->ignore(
|
||||
\Illuminate\Filesystem\Cache::class,
|
||||
\Illuminate\Log\LogManager::class,
|
||||
\Illuminate\Http\Testing\File::class,
|
||||
\Illuminate\Http\UploadedFile::class,
|
||||
\Illuminate\Support\Carbon::class,
|
||||
)
|
||||
->load();
|
6
public/flutter_service_worker.js
vendored
6
public/flutter_service_worker.js
vendored
@ -4,9 +4,9 @@ const TEMP = 'flutter-temp-cache';
|
||||
const CACHE_NAME = 'flutter-app-cache';
|
||||
const RESOURCES = {
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"main.dart.js": "5b1a19e00c074ba73b725b51f90da896",
|
||||
"main.dart.js": "da1ca4eca714583ff82c65cd3792029b",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
"/": "eb40da80095b8fd3e80ef1eeb7fb0e22",
|
||||
"/": "384495013e69b7963a1f0883fc10f045",
|
||||
"flutter.js": "f85e6fb278b0fd20c349186fb46ae36d",
|
||||
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
|
||||
"canvaskit/canvaskit.js": "2bc454a691c631b07a9307ac4ca47797",
|
||||
@ -299,7 +299,7 @@ const RESOURCES = {
|
||||
"assets/assets/google_fonts/Roboto-Regular.ttf": "8a36205bd9b83e03af0591a004bc97f4",
|
||||
"assets/fonts/MaterialIcons-Regular.otf": "95db9098c58fd6db106f1116bae85a0b",
|
||||
"assets/NOTICES": "1a34e70168d56fad075adfb4bdbb20eb",
|
||||
"version.json": "674c2b7791134c91ed5f7bc241b11d8d",
|
||||
"version.json": "ecddd9a09423be80c670ca3af19cf971",
|
||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed"
|
||||
};
|
||||
|
315425
public/main.dart.js
vendored
315425
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
266517
public/main.foss.dart.js
vendored
266517
public/main.foss.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
22096
public/main.profile.dart.js
vendored
22096
public/main.profile.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
{"app_name":"invoiceninja_flutter","version":"5.0.95","build_number":"95","package_name":"invoiceninja_flutter"}
|
||||
{"app_name":"invoiceninja_flutter","version":"5.0.99","build_number":"99","package_name":"invoiceninja_flutter"}
|
@ -77,11 +77,6 @@
|
||||
{{ ctrans('texts.name') }}
|
||||
</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||
<span role="button" wire:click="sortBy('type')" class="cursor-pointer">
|
||||
{{ ctrans('texts.type') }}
|
||||
</span>
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-primary text-left text-xs leading-4 font-medium text-white uppercase tracking-wider">
|
||||
<span role="button" wire:click="sortBy('size')" class="cursor-pointer">
|
||||
{{ ctrans('texts.size') }}
|
||||
@ -102,9 +97,7 @@
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500">
|
||||
{{ Illuminate\Support\Str::limit($document->name, 20) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500">
|
||||
{{ App\Models\Document::$types[$document->type]['mime'] }}
|
||||
</td>
|
||||
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500">
|
||||
{{ $document->size / 1000 }} kB
|
||||
</td>
|
||||
|
@ -24,14 +24,6 @@
|
||||
{{ Illuminate\Support\Str::limit($document->name, 40) }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.type') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ App\Models\Document::$types[$document->type]['mime'] }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.hash') }}
|
||||
|
@ -43,7 +43,7 @@
|
||||
@endsection
|
||||
|
||||
@section('gateway_footer')
|
||||
@if($gateway->forte->company_gateway->getConfigField('testMode'))
|
||||
@if($gateway->company_gateway->getConfigField('testMode'))
|
||||
<script type="text/javascript" src="https://sandbox.forte.net/api/js/v1"></script>
|
||||
@else
|
||||
<script type="text/javascript" src="https://api.forte.net/js/v1"></script>
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Country;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
@ -401,4 +402,89 @@ class ClientApiTest extends TestCase
|
||||
|
||||
$response->assertStatus(302);
|
||||
}
|
||||
|
||||
public function testRoundingDecimalsTwo()
|
||||
{
|
||||
$currency = $this->company;
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(0.05, $currency);
|
||||
|
||||
$this->assertEquals(0.05, $x);
|
||||
}
|
||||
|
||||
public function testRoundingDecimalsThree()
|
||||
{
|
||||
$currency = $this->company;
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(0.005, $currency);
|
||||
|
||||
$this->assertEquals(0.005, $x);
|
||||
}
|
||||
|
||||
public function testRoundingDecimalsFour()
|
||||
{
|
||||
$currency = $this->company;
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(0.0005, $currency);
|
||||
|
||||
$this->assertEquals(0.0005, $x);
|
||||
}
|
||||
|
||||
public function testRoundingDecimalsFive()
|
||||
{
|
||||
$currency = $this->company;
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(0.00005, $currency);
|
||||
|
||||
$this->assertEquals(0.00005, $x);
|
||||
}
|
||||
|
||||
public function testRoundingDecimalsSix()
|
||||
{
|
||||
$currency = $this->company;
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(0.000005, $currency);
|
||||
|
||||
$this->assertEquals(0.000005, $x);
|
||||
}
|
||||
|
||||
public function testRoundingDecimalsSeven()
|
||||
{
|
||||
$currency = $this->company;
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(0.0000005, $currency);
|
||||
|
||||
$this->assertEquals(0.0000005, $x);
|
||||
}
|
||||
|
||||
public function testRoundingDecimalsEight()
|
||||
{
|
||||
$currency = $this->company;
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(0.00000005, $currency);
|
||||
|
||||
$this->assertEquals(0.00000005, $x);
|
||||
}
|
||||
|
||||
public function testRoundingPositive()
|
||||
{
|
||||
$currency = $this->company;
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(1.5, $currency);
|
||||
$this->assertEquals(1.5, $x);
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(1.50, $currency);
|
||||
$this->assertEquals(1.5, $x);
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(1.500, $currency);
|
||||
$this->assertEquals(1.5, $x);
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(1.50005, $currency);
|
||||
$this->assertEquals(1.50005, $x);
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(1.50000005, $currency);
|
||||
$this->assertEquals(1.50000005, $x);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -166,11 +166,12 @@ class RecurringInvoiceTest extends TestCase
|
||||
'company_id' => $this->company->id,
|
||||
]);
|
||||
});
|
||||
$client = Client::all()->first();
|
||||
|
||||
$client = Client::query()->orderBy('id', 'DESC')->first();
|
||||
|
||||
RecurringInvoice::factory()->create(['user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $this->client->id]);
|
||||
|
||||
$RecurringInvoice = RecurringInvoice::where('user_id', $this->user->id)->first();
|
||||
$RecurringInvoice = RecurringInvoice::query()->where('user_id', $this->user->id)->orderBy('id', 'DESC')->first();
|
||||
$RecurringInvoice->save();
|
||||
|
||||
$response = $this->withHeaders([
|
||||
|
@ -63,7 +63,7 @@ class RecurringQuoteTest extends TestCase
|
||||
{
|
||||
RecurringQuote::factory()->create(['user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $this->client->id]);
|
||||
|
||||
$RecurringQuote = RecurringQuote::where('user_id', $this->user->id)->first();
|
||||
$RecurringQuote = RecurringQuote::query()->where('user_id', $this->user->id)->orderBy('id','DESC')->first();
|
||||
$RecurringQuote->save();
|
||||
|
||||
$response = $this->withHeaders([
|
||||
|
@ -58,37 +58,5 @@ class DownloadHistoricalInvoiceTest extends TestCase
|
||||
$this->assertNotNull($this->invoice->activities);
|
||||
}
|
||||
|
||||
public function testBackupExists()
|
||||
{
|
||||
$this->mockActivity();
|
||||
|
||||
$this->assertNotNull($this->invoice->activities->first()->backup->html_backup);
|
||||
}
|
||||
|
||||
public function testBackupDownload()
|
||||
{
|
||||
$this->mockActivity();
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->get('/api/v1/activities/download_entity/'.$this->encodePrimaryKey($this->invoice->activities->first()->id));
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
|
||||
public function testBackupCheckPriorToDownloadWorks()
|
||||
{
|
||||
$this->mockActivity();
|
||||
|
||||
$backup = $this->invoice->activities->first()->backup;
|
||||
$backup->forceDelete();
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->get('/api/v1/activities/download_entity/'.$this->encodePrimaryKey($this->invoice->activities->first()->id));
|
||||
|
||||
$response->assertStatus(404);
|
||||
}
|
||||
}
|
||||
|
@ -89,18 +89,6 @@ class NumberTest extends TestCase
|
||||
$this->assertEquals(2.15, $rounded);
|
||||
}
|
||||
|
||||
//this method proved an error! removing this method from production
|
||||
// public function testImportFloatConversion()
|
||||
// {
|
||||
|
||||
// $amount = '€7,99';
|
||||
|
||||
// $converted_amount = Number::parseStringFloat($amount);
|
||||
|
||||
// $this->assertEquals(799, $converted_amount);
|
||||
|
||||
// }
|
||||
|
||||
public function testParsingStringCurrency()
|
||||
{
|
||||
$amount = '€7,99';
|
||||
@ -110,86 +98,4 @@ class NumberTest extends TestCase
|
||||
$this->assertEquals(7.99, $converted_amount);
|
||||
}
|
||||
|
||||
public function testRoundingDecimalsTwo()
|
||||
{
|
||||
$currency = Currency::find(1);
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(0.05, $currency);
|
||||
|
||||
$this->assertEquals(0.05, $x);
|
||||
}
|
||||
|
||||
public function testRoundingDecimalsThree()
|
||||
{
|
||||
$currency = Currency::find(1);
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(0.005, $currency);
|
||||
|
||||
$this->assertEquals(0.005, $x);
|
||||
}
|
||||
|
||||
public function testRoundingDecimalsFour()
|
||||
{
|
||||
$currency = Currency::find(1);
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(0.0005, $currency);
|
||||
|
||||
$this->assertEquals(0.0005, $x);
|
||||
}
|
||||
|
||||
public function testRoundingDecimalsFive()
|
||||
{
|
||||
$currency = Currency::find(1);
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(0.00005, $currency);
|
||||
|
||||
$this->assertEquals(0.00005, $x);
|
||||
}
|
||||
|
||||
public function testRoundingDecimalsSix()
|
||||
{
|
||||
$currency = Currency::find(1);
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(0.000005, $currency);
|
||||
|
||||
$this->assertEquals(0.000005, $x);
|
||||
}
|
||||
|
||||
public function testRoundingDecimalsSeven()
|
||||
{
|
||||
$currency = Currency::find(1);
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(0.0000005, $currency);
|
||||
|
||||
$this->assertEquals(0.0000005, $x);
|
||||
}
|
||||
|
||||
public function testRoundingDecimalsEight()
|
||||
{
|
||||
$currency = Currency::find(1);
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(0.00000005, $currency);
|
||||
|
||||
$this->assertEquals(0.00000005, $x);
|
||||
}
|
||||
|
||||
public function testRoundingPositive()
|
||||
{
|
||||
$currency = Currency::find(1);
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(1.5, $currency);
|
||||
$this->assertEquals(1.5, $x);
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(1.50, $currency);
|
||||
$this->assertEquals(1.5, $x);
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(1.500, $currency);
|
||||
$this->assertEquals(1.5, $x);
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(1.50005, $currency);
|
||||
$this->assertEquals(1.50005, $x);
|
||||
|
||||
$x = Number::formatValueNoTrailingZeroes(1.50000005, $currency);
|
||||
$this->assertEquals(1.50000005, $x);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user