mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-10 05:02:36 +01:00
commit
fe9ae17943
@ -1 +1 @@
|
||||
5.10.4
|
||||
5.10.5
|
@ -12,37 +12,38 @@
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App;
|
||||
use App\Models\User;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Account;
|
||||
use App\Models\Company;
|
||||
use App\Models\Contact;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Payment;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\CompanyUser;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Models\CompanyToken;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\CompanyLedger;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\VendorContact;
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\QuoteInvitation;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use App\Factory\ClientContactFactory;
|
||||
use App\Factory\VendorContactFactory;
|
||||
use App\Jobs\Company\CreateCompanyToken;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Account;
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyLedger;
|
||||
use App\Models\CompanyToken;
|
||||
use App\Models\CompanyUser;
|
||||
use App\Models\Contact;
|
||||
use App\Models\Credit;
|
||||
use App\Models\CreditInvitation;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\InvoiceInvitation;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\QuoteInvitation;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\RecurringInvoiceInvitation;
|
||||
use App\Models\User;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\VendorContact;
|
||||
use App\Utils\Ninja;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
/*
|
||||
@ -130,6 +131,7 @@ class CheckData extends Command
|
||||
$this->checkContactEmailAndSendEmailStatus();
|
||||
$this->checkPaymentCurrency();
|
||||
$this->checkSubdomainsSet();
|
||||
$this->checkExpenseCurrency();
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
$this->checkAccountStatuses();
|
||||
@ -1158,7 +1160,21 @@ class CheckData extends Command
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function checkExpenseCurrency()
|
||||
{
|
||||
Expense::with('company')
|
||||
->withTrashed()
|
||||
->whereNull('exchange_rate')
|
||||
->orWhere('exchange_rate', 0)
|
||||
->cursor()
|
||||
->each(function ($expense){
|
||||
$expense->exchange_rate = 1;
|
||||
$expense->saveQuietly();
|
||||
|
||||
$this->logMessage("Fixing - exchange rate for expense :: {$expense->id}");
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -62,10 +62,10 @@ class SendRemindersCron extends Command
|
||||
public function handle()
|
||||
{
|
||||
Invoice::where('next_send_date', '<=', now()->toDateTimeString())
|
||||
->whereNull('deleted_at')
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('balance', '>', 0)
|
||||
->whereNull('invoices.deleted_at')
|
||||
->where('invoices.is_deleted', 0)
|
||||
->whereIn('invoices.status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('invoices.balance', '>', 0)
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
@ -73,6 +73,7 @@ class SendRemindersCron extends Command
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled', 0);
|
||||
})
|
||||
|
||||
->with('invitations')->cursor()->each(function ($invoice) {
|
||||
if ($invoice->isPayable()) {
|
||||
$reminder_template = $invoice->calculateTemplate('invoice');
|
||||
|
@ -320,7 +320,7 @@ class InvoiceFilters extends QueryFilters
|
||||
{
|
||||
$sort_col = explode('|', $sort);
|
||||
|
||||
if (!is_array($sort_col) || count($sort_col) != 2) {
|
||||
if (!is_array($sort_col) || count($sort_col) != 2 || in_array($sort_col[0], ['documents'])) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
|
@ -107,16 +107,16 @@ class InvoiceSumInclusive
|
||||
private function calculateCustomValues()
|
||||
{
|
||||
|
||||
$this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge1, $this->invoice->custom_surcharge_tax1);
|
||||
// $this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge1, $this->invoice->custom_surcharge_tax1);
|
||||
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge1);
|
||||
|
||||
$this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge2, $this->invoice->custom_surcharge_tax2);
|
||||
// $this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge2, $this->invoice->custom_surcharge_tax2);
|
||||
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge2);
|
||||
|
||||
$this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge3, $this->invoice->custom_surcharge_tax3);
|
||||
// $this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge3, $this->invoice->custom_surcharge_tax3);
|
||||
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge3);
|
||||
|
||||
$this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge4, $this->invoice->custom_surcharge_tax4);
|
||||
// $this->total_taxes += $this->multiInclusiveTax($this->invoice->custom_surcharge4, $this->invoice->custom_surcharge_tax4);
|
||||
$this->total_custom_values += $this->valuer($this->invoice->custom_surcharge4);
|
||||
|
||||
$this->total += $this->total_custom_values;
|
||||
@ -137,21 +137,21 @@ class InvoiceSumInclusive
|
||||
}
|
||||
|
||||
//Handles cases where the surcharge is not taxed
|
||||
// if(is_numeric($this->invoice->custom_surcharge1) && $this->invoice->custom_surcharge1 > 0 && !$this->invoice->custom_surcharge_tax1) {
|
||||
// $amount += $this->invoice->custom_surcharge1;
|
||||
// }
|
||||
if(is_numeric($this->invoice->custom_surcharge1) && $this->invoice->custom_surcharge1 > 0 && $this->invoice->custom_surcharge_tax1) {
|
||||
$amount += $this->invoice->custom_surcharge1;
|
||||
}
|
||||
|
||||
// if(is_numeric($this->invoice->custom_surcharge2) && $this->invoice->custom_surcharge2 > 0 && !$this->invoice->custom_surcharge_tax2) {
|
||||
// $amount += $this->invoice->custom_surcharge2;
|
||||
// }
|
||||
if(is_numeric($this->invoice->custom_surcharge2) && $this->invoice->custom_surcharge2 > 0 && $this->invoice->custom_surcharge_tax2) {
|
||||
$amount += $this->invoice->custom_surcharge2;
|
||||
}
|
||||
|
||||
// if(is_numeric($this->invoice->custom_surcharge3) && $this->invoice->custom_surcharge3 > 0 && !$this->invoice->custom_surcharge_tax3) {
|
||||
// $amount += $this->invoice->custom_surcharge3;
|
||||
// }
|
||||
if(is_numeric($this->invoice->custom_surcharge3) && $this->invoice->custom_surcharge3 > 0 && $this->invoice->custom_surcharge_tax3) {
|
||||
$amount += $this->invoice->custom_surcharge3;
|
||||
}
|
||||
|
||||
// if(is_numeric($this->invoice->custom_surcharge4) && $this->invoice->custom_surcharge4 > 0 && !$this->invoice->custom_surcharge_tax4) {
|
||||
// $amount += $this->invoice->custom_surcharge4;
|
||||
// }
|
||||
if(is_numeric($this->invoice->custom_surcharge4) && $this->invoice->custom_surcharge4 > 0 && $this->invoice->custom_surcharge_tax4) {
|
||||
$amount += $this->invoice->custom_surcharge4;
|
||||
}
|
||||
|
||||
if (is_string($this->invoice->tax_name1) && strlen($this->invoice->tax_name1) > 1) {
|
||||
$tax = $this->calcInclusiveLineTax($this->invoice->tax_rate1, $amount);
|
||||
|
@ -81,14 +81,20 @@ class SearchController extends Controller
|
||||
$invoices = Invoice::query()
|
||||
->company()
|
||||
->with('client')
|
||||
->where('is_deleted', 0)
|
||||
->whereHas('client', function ($q) {
|
||||
$q->where('is_deleted', 0);
|
||||
})
|
||||
->where('invoices.is_deleted', 0)
|
||||
// ->whereHas('client', function ($q) {
|
||||
// $q->where('is_deleted', 0);
|
||||
// })
|
||||
|
||||
->leftJoin('clients', function ($join) {
|
||||
$join->on('invoices.client_id', '=', 'clients.id')
|
||||
->where('clients.is_deleted', 0);
|
||||
})
|
||||
|
||||
->when(!$user->hasPermission('view_all') || !$user->hasPermission('view_invoice'), function ($query) use ($user) {
|
||||
$query->where('user_id', $user->id);
|
||||
$query->where('invoices.user_id', $user->id);
|
||||
})
|
||||
->orderBy('id', 'desc')
|
||||
->orderBy('invoices.id', 'desc')
|
||||
->take(3000)
|
||||
->get();
|
||||
|
||||
|
@ -78,7 +78,9 @@ class StoreInvoiceRequest extends Request
|
||||
$rules['tax_name3'] = 'bail|sometimes|string|nullable';
|
||||
$rules['exchange_rate'] = 'bail|sometimes|numeric';
|
||||
$rules['partial'] = 'bail|sometimes|nullable|numeric|gte:0';
|
||||
$rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date'];
|
||||
$rules['partial_due_date'] = ['bail', 'sometimes', 'nullable', 'exclude_if:partial,0', 'date', 'before:due_date', 'after_or_equal:date'];
|
||||
$rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date ?? '') > 1), 'date'];
|
||||
|
||||
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
|
||||
|
||||
// $rules['amount'] = ['sometimes', 'bail', 'max:99999999999999'];
|
||||
|
@ -82,8 +82,8 @@ class UpdateInvoiceRequest extends Request
|
||||
|
||||
$rules['date'] = 'bail|sometimes|date:Y-m-d';
|
||||
|
||||
// $rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date'];
|
||||
// $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date'];
|
||||
$rules['partial_due_date'] = ['bail', 'sometimes', 'nullable', 'exclude_if:partial,0', 'date', 'before:due_date', 'after_or_equal:date'];
|
||||
$rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', 'after_or_equal:date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date'];
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
@ -80,8 +80,8 @@ class StoreProjectRequest extends Request
|
||||
$input['budgeted_hours'] = 0;
|
||||
}
|
||||
|
||||
$input['task_rate'] = isset($input['task_rate']) ? $input['task_rate'] : 0;
|
||||
|
||||
$input['task_rate'] = (isset($input['task_rate']) && floatval($input['task_rate']) >= 0) ? $input['task_rate'] : 0;
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,8 @@ class UpdateProjectRequest extends Request
|
||||
$rules['number'] = Rule::unique('projects')->where('company_id', $user->company()->id)->ignore($this->project->id);
|
||||
}
|
||||
|
||||
$rules['budgeted_hours'] = 'sometimes|numeric';
|
||||
$rules['budgeted_hours'] = 'sometimes|bail|numeric';
|
||||
$rules['task_rate'] = 'sometimes|bail|numeric';
|
||||
|
||||
if ($this->file('documents') && is_array($this->file('documents'))) {
|
||||
$rules['documents.*'] = $this->fileValidation();
|
||||
|
@ -66,8 +66,8 @@ class StoreQuoteRequest extends Request
|
||||
$rules['exchange_rate'] = 'bail|sometimes|numeric';
|
||||
$rules['line_items'] = 'array';
|
||||
$rules['date'] = 'bail|sometimes|date:Y-m-d';
|
||||
$rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date', 'after_or_equal:date'];
|
||||
$rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date'];
|
||||
$rules['partial_due_date'] = ['bail', 'sometimes', 'nullable', 'exclude_if:partial,0', 'date', 'before:due_date', 'after_or_equal:date'];
|
||||
$rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date ?? '') > 1), 'date'];
|
||||
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
|
||||
|
||||
return $rules;
|
||||
|
@ -65,7 +65,7 @@ class UpdateQuoteRequest extends Request
|
||||
|
||||
$rules['date'] = 'bail|sometimes|date:Y-m-d';
|
||||
|
||||
$rules['partial_due_date'] = ['bail', 'sometimes', 'exclude_if:partial,0', Rule::requiredIf(fn () => $this->partial > 0), 'date', 'before:due_date'];
|
||||
$rules['partial_due_date'] = ['bail', 'sometimes', 'nullable', 'exclude_if:partial,0', 'date', 'before:due_date', 'after_or_equal:date'];
|
||||
$rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', 'after_or_equal:date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date'];
|
||||
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
|
||||
|
||||
|
@ -11,16 +11,17 @@
|
||||
|
||||
namespace App\Jobs\Cron;
|
||||
|
||||
use App\Events\Expense\ExpenseWasCreated;
|
||||
use App\Factory\RecurringExpenseToExpenseFactory;
|
||||
use App\Utils\Ninja;
|
||||
use App\Libraries\MultiDB;
|
||||
use Illuminate\Support\Carbon;
|
||||
use App\Models\RecurringExpense;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use App\Events\Expense\ExpenseWasCreated;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use App\Factory\RecurringExpenseToExpenseFactory;
|
||||
use App\Libraries\Currency\Conversion\CurrencyApi;
|
||||
|
||||
class RecurringExpensesCron
|
||||
{
|
||||
@ -109,6 +110,15 @@ class RecurringExpensesCron
|
||||
$expense->payment_date = now()->format('Y-m-d');
|
||||
}
|
||||
|
||||
if ((int)$expense->company->settings->currency_id != $expense->currency_id) {
|
||||
$exchange_rate = new CurrencyApi();
|
||||
|
||||
$expense->exchange_rate = $exchange_rate->exchangeRate($expense->currency_id, (int)$expense->company->settings->currency_id, Carbon::parse($expense->date));
|
||||
}
|
||||
else {
|
||||
$expense->exchange_rate = 1;
|
||||
}
|
||||
|
||||
$expense->number = $this->getNextExpenseNumber($expense);
|
||||
$expense->saveQuietly();
|
||||
|
||||
|
@ -48,12 +48,12 @@ class RecurringInvoicesCron
|
||||
Auth::logout();
|
||||
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
$recurring_invoices = RecurringInvoice::query()->where('status_id', RecurringInvoice::STATUS_ACTIVE)
|
||||
->where('is_deleted', false)
|
||||
->where('remaining_cycles', '!=', '0')
|
||||
->whereNotNull('next_send_date')
|
||||
->whereNull('deleted_at')
|
||||
->where('next_send_date', '<=', now()->toDateTimeString())
|
||||
$recurring_invoices = RecurringInvoice::query()->where('recurring_invoices.status_id', RecurringInvoice::STATUS_ACTIVE)
|
||||
->where('recurring_invoices.is_deleted', false)
|
||||
->where('recurring_invoices.remaining_cycles', '!=', '0')
|
||||
->whereNotNull('recurring_invoices.next_send_date')
|
||||
->whereNull('recurring_invoices.deleted_at')
|
||||
->where('recurring_invoices.next_send_date', '<=', now()->toDateTimeString())
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
@ -87,18 +87,27 @@ class RecurringInvoicesCron
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
|
||||
$recurring_invoices = RecurringInvoice::query()->where('status_id', RecurringInvoice::STATUS_ACTIVE)
|
||||
->where('is_deleted', false)
|
||||
->where('remaining_cycles', '!=', '0')
|
||||
->whereNull('deleted_at')
|
||||
->whereNotNull('next_send_date')
|
||||
->where('next_send_date', '<=', now()->toDateTimeString())
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
$recurring_invoices = RecurringInvoice::query()->where('recurring_invoices.status_id', RecurringInvoice::STATUS_ACTIVE)
|
||||
->where('recurring_invoices.is_deleted', false)
|
||||
->where('recurring_invoices.remaining_cycles', '!=', '0')
|
||||
->whereNull('recurring_invoices.deleted_at')
|
||||
->whereNotNull('recurring_invoices.next_send_date')
|
||||
->where('recurring_invoices.next_send_date', '<=', now()->toDateTimeString())
|
||||
// ->whereHas('client', function ($query) {
|
||||
// $query->where('is_deleted', 0)
|
||||
// ->where('deleted_at', null);
|
||||
// })
|
||||
// ->whereHas('company', function ($query) {
|
||||
// $query->where('is_disabled', 0);
|
||||
// })
|
||||
->leftJoin('clients', function ($join) {
|
||||
$join->on('recurring_invoices.client_id', '=', 'clients.id')
|
||||
->where('clients.is_deleted', 0)
|
||||
->whereNull('clients.deleted_at');
|
||||
})
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled', 0);
|
||||
->leftJoin('companies', function ($join) {
|
||||
$join->on('recurring_invoices.company_id', '=', 'companies.id')
|
||||
->where('companies.is_disabled', 0);
|
||||
})
|
||||
->with('company')
|
||||
->cursor();
|
||||
|
@ -51,20 +51,29 @@ class InvoiceCheckLateWebhook implements ShouldQueue
|
||||
->pluck('company_id');
|
||||
|
||||
Invoice::query()
|
||||
->where('is_deleted', false)
|
||||
->whereNull('deleted_at')
|
||||
->whereNotNull('due_date')
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('balance', '>', 0)
|
||||
->whereIn('company_id', $company_ids)
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
})
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled', 0);
|
||||
})
|
||||
->whereBetween('due_date', [now()->subDay()->startOfDay(), now()->startOfDay()->subSecond()])
|
||||
->where('invoices.is_deleted', false)
|
||||
->whereNull('invoices.deleted_at')
|
||||
->whereNotNull('invoices.due_date')
|
||||
->whereIn('invoices.status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('invoices.balance', '>', 0)
|
||||
->whereIn('invoices.company_id', $company_ids)
|
||||
// ->whereHas('client', function ($query) {
|
||||
// $query->where('is_deleted', 0)
|
||||
// ->where('deleted_at', null);
|
||||
// })
|
||||
// ->whereHas('company', function ($query) {
|
||||
// $query->where('is_disabled', 0);
|
||||
// })
|
||||
->leftJoin('clients', function ($join) {
|
||||
$join->on('invoices.client_id', '=', 'clients.id')
|
||||
->where('clients.is_deleted', 0)
|
||||
->whereNull('clients.deleted_at');
|
||||
})
|
||||
->leftJoin('companies', function ($join) {
|
||||
$join->on('invoices.company_id', '=', 'companies.id')
|
||||
->where('companies.is_disabled', 0);
|
||||
})
|
||||
->whereBetween('invoices.due_date', [now()->subDay()->startOfDay(), now()->startOfDay()->subSecond()])
|
||||
->cursor()
|
||||
->each(function ($invoice) {
|
||||
(new WebhookHandler(Webhook::EVENT_LATE_INVOICE, $invoice, $invoice->company, 'client'))->handle();
|
||||
@ -78,20 +87,29 @@ class InvoiceCheckLateWebhook implements ShouldQueue
|
||||
->pluck('company_id');
|
||||
|
||||
Invoice::query()
|
||||
->where('is_deleted', false)
|
||||
->whereNull('deleted_at')
|
||||
->whereNotNull('due_date')
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('balance', '>', 0)
|
||||
->whereIn('company_id', $company_ids)
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
})
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled', 0);
|
||||
})
|
||||
->whereBetween('due_date', [now()->subDay()->startOfDay(), now()->startOfDay()->subSecond()])
|
||||
->where('invoices.is_deleted', false)
|
||||
->whereNull('invoices.deleted_at')
|
||||
->whereNotNull('invoices.due_date')
|
||||
->whereIn('invoices.status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('invoices.balance', '>', 0)
|
||||
->whereIn('invoices.company_id', $company_ids)
|
||||
// ->whereHas('client', function ($query) {
|
||||
// $query->where('is_deleted', 0)
|
||||
// ->where('deleted_at', null);
|
||||
// })
|
||||
// ->whereHas('company', function ($query) {
|
||||
// $query->where('is_disabled', 0);
|
||||
// })
|
||||
->leftJoin('clients', function ($join) {
|
||||
$join->on('invoices.client_id', '=', 'clients.id')
|
||||
->where('clients.is_deleted', 0)
|
||||
->whereNull('clients.deleted_at');
|
||||
})
|
||||
->leftJoin('companies', function ($join) {
|
||||
$join->on('invoices.company_id', '=', 'companies.id')
|
||||
->where('companies.is_disabled', 0);
|
||||
})
|
||||
->whereBetween('invoices.due_date', [now()->subDay()->startOfDay(), now()->startOfDay()->subSecond()])
|
||||
->cursor()
|
||||
->each(function ($invoice) {
|
||||
(new WebhookHandler(Webhook::EVENT_LATE_INVOICE, $invoice, $invoice->company, 'client'))->handle();
|
||||
|
@ -49,10 +49,10 @@ class QuoteCheckExpired implements ShouldQueue
|
||||
{
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
Quote::query()
|
||||
->where('status_id', Quote::STATUS_SENT)
|
||||
->where('is_deleted', false)
|
||||
->whereNull('deleted_at')
|
||||
->whereNotNull('due_date')
|
||||
->where('quotes.status_id', Quote::STATUS_SENT)
|
||||
->where('quotes.is_deleted', false)
|
||||
->whereNull('quotes.deleted_at')
|
||||
->whereNotNull('quotes.due_date')
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
@ -60,8 +60,8 @@ class QuoteCheckExpired implements ShouldQueue
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled', 0);
|
||||
})
|
||||
// ->where('due_date', '<='. now()->toDateTimeString())
|
||||
->whereBetween('due_date', [now()->subDay()->startOfDay(), now()->startOfDay()->subSecond()])
|
||||
|
||||
->whereBetween('quotes.due_date', [now()->subDay()->startOfDay(), now()->startOfDay()->subSecond()])
|
||||
->cursor()
|
||||
->each(function ($quote) {
|
||||
$this->queueExpiredQuoteNotification($quote);
|
||||
@ -71,10 +71,10 @@ class QuoteCheckExpired implements ShouldQueue
|
||||
MultiDB::setDB($db);
|
||||
|
||||
Quote::query()
|
||||
->where('status_id', Quote::STATUS_SENT)
|
||||
->where('is_deleted', false)
|
||||
->whereNull('deleted_at')
|
||||
->whereNotNull('due_date')
|
||||
->where('quotes.status_id', Quote::STATUS_SENT)
|
||||
->where('quotes.is_deleted', false)
|
||||
->whereNull('quotes.deleted_at')
|
||||
->whereNotNull('quotes.due_date')
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
@ -82,8 +82,8 @@ class QuoteCheckExpired implements ShouldQueue
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled', 0);
|
||||
})
|
||||
// ->where('due_date', '<='. now()->toDateTimeString())
|
||||
->whereBetween('due_date', [now()->subDay()->startOfDay(), now()->startOfDay()->subSecond()])
|
||||
|
||||
->whereBetween('quotes.due_date', [now()->subDay()->startOfDay(), now()->startOfDay()->subSecond()])
|
||||
->cursor()
|
||||
->each(function ($quote) {
|
||||
$this->queueExpiredQuoteNotification($quote);
|
||||
|
@ -61,10 +61,10 @@ class QuoteReminderJob implements ShouldQueue
|
||||
nrlog("Sending quote reminders on ".now()->format('Y-m-d h:i:s'));
|
||||
|
||||
Quote::query()
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT])
|
||||
->whereNull('deleted_at')
|
||||
->where('next_send_date', '<=', now()->toDateTimeString())
|
||||
->where('quotes.is_deleted', 0)
|
||||
->whereIn('quotes.status_id', [Invoice::STATUS_SENT])
|
||||
->whereNull('quotes.deleted_at')
|
||||
->where('quotes.next_send_date', '<=', now()->toDateTimeString())
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
@ -88,10 +88,10 @@ class QuoteReminderJob implements ShouldQueue
|
||||
nrlog("Sending quote reminders on db {$db} ".now()->format('Y-m-d h:i:s'));
|
||||
|
||||
Quote::query()
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT])
|
||||
->whereNull('deleted_at')
|
||||
->where('next_send_date', '<=', now()->toDateTimeString())
|
||||
->where('quotes.is_deleted', 0)
|
||||
->whereIn('quotes.status_id', [Invoice::STATUS_SENT])
|
||||
->whereNull('quotes.deleted_at')
|
||||
->where('quotes.next_send_date', '<=', now()->toDateTimeString())
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
@ -99,6 +99,7 @@ class QuoteReminderJob implements ShouldQueue
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled', 0);
|
||||
})
|
||||
|
||||
->with('invitations')->chunk(50, function ($quotes) {
|
||||
|
||||
foreach ($quotes as $quote) {
|
||||
|
@ -47,11 +47,10 @@ class ExpenseRepository extends BaseRepository
|
||||
$user = auth()->user();
|
||||
|
||||
$payment_date = &$data['payment_date'];
|
||||
$vendor_id = &$data['vendor_id'];
|
||||
|
||||
if($payment_date && $payment_date == $expense->payment_date) {
|
||||
//do nothing
|
||||
} elseif($payment_date && strlen($payment_date) > 1 && $user->company()->notify_vendor_when_paid && ($vendor_id || $expense->vendor_id)) {
|
||||
} elseif($payment_date && strlen($payment_date) > 1 && $user->company()->notify_vendor_when_paid && (isset($data['vendor_id']) || $expense->vendor_id)) {
|
||||
$this->notify_vendor = true;
|
||||
}
|
||||
|
||||
|
@ -117,10 +117,15 @@ class TaskRepository extends BaseRepository
|
||||
}
|
||||
|
||||
$key_values = array_column($time_log, 0);
|
||||
array_multisort($key_values, SORT_ASC, $time_log);
|
||||
|
||||
if(count($key_values) > 0)
|
||||
array_multisort($key_values, SORT_ASC, $time_log);
|
||||
|
||||
foreach($time_log as $key => $value) {
|
||||
$time_log[$key][1] = $this->roundTimeLog($time_log[$key][0], $time_log[$key][1]);
|
||||
|
||||
if(is_array($time_log[$key]) && count($time_log[$key]) >=2)
|
||||
$time_log[$key][1] = $this->roundTimeLog($time_log[$key][0], $time_log[$key][1]);
|
||||
|
||||
}
|
||||
|
||||
if (isset($data['action'])) {
|
||||
|
@ -90,15 +90,15 @@ class ARDetailReport extends BaseExport
|
||||
$this->csv->insertOne($this->buildHeader());
|
||||
|
||||
$query = Invoice::query()
|
||||
->whereIn('invoices.status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->withTrashed()
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0);
|
||||
})
|
||||
->where('company_id', $this->company->id)
|
||||
->where('is_deleted', 0)
|
||||
->where('balance', '>', 0)
|
||||
->orderBy('due_date', 'ASC')
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]);
|
||||
->where('invoices.company_id', $this->company->id)
|
||||
->where('invoices.is_deleted', 0)
|
||||
->where('invoices.balance', '>', 0)
|
||||
->orderBy('invoices.due_date', 'ASC');
|
||||
|
||||
$query = $this->addDateRange($query, 'invoices');
|
||||
|
||||
|
@ -39,7 +39,7 @@ trait PdfMaker
|
||||
$pdf->addChromiumArguments(config('ninja.snappdf_chromium_arguments'));
|
||||
}
|
||||
|
||||
$html = str_replace(['file:/', 'iframe', '<object', '<object', '127.0.0.1', 'localhost'], ['','','','','',''], $html);
|
||||
$html = str_ireplace(['file:/', 'iframe', '<embed', '<embed', '<object', '<object', '127.0.0.1', 'localhost'], '', $html);
|
||||
|
||||
$generated = $pdf
|
||||
->setHtml($html)
|
||||
|
@ -17,8 +17,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => env('APP_VERSION', '5.10.4'),
|
||||
'app_tag' => env('APP_TAG', '5.10.4'),
|
||||
'app_version' => env('APP_VERSION', '5.10.5'),
|
||||
'app_tag' => env('APP_TAG', '5.10.5'),
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', false),
|
||||
|
4
public/flutter_service_worker.js
vendored
4
public/flutter_service_worker.js
vendored
@ -18,7 +18,7 @@ const RESOURCES = {"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"canvaskit/skwasm.wasm": "e42815763c5d05bba43f9d0337fa7d84",
|
||||
"version.json": "f789e711f61e122f41a7eda7522a1fba",
|
||||
"favicon.png": "dca91c54388f52eded692718d5a98b8b",
|
||||
"main.dart.js": "a47a0b8efc63ee566243666241e5ee83",
|
||||
"main.dart.js": "6acd51f637b30ad5fc3bc0ad9ebb3cf0",
|
||||
"assets/NOTICES": "412b336cf9e33e70058d612857effae1",
|
||||
"assets/AssetManifest.bin": "bf3be26e7055ad9a32f66b3a56138224",
|
||||
"assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219",
|
||||
@ -307,7 +307,7 @@ const RESOURCES = {"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||
"assets/FontManifest.json": "087fb858dc3cbfbf6baf6a30004922f1",
|
||||
"assets/fonts/MaterialIcons-Regular.otf": "a57618538ab8b4c4081d4491870ac333",
|
||||
"assets/AssetManifest.json": "759f9ef9973f7e26c2a51450b55bb9fa",
|
||||
"/": "823d449f1ccbed02fb35bdfff5b50ab9",
|
||||
"/": "09dd63a64a586d8b9ee7b21d2761a403",
|
||||
"favicon.ico": "51636d3a390451561744c42188ccd628",
|
||||
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40"};
|
||||
// The application shell files that are downloaded before a service worker can
|
||||
|
260708
public/main.dart.js
vendored
260708
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
254378
public/main.foss.dart.js
vendored
254378
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
17733
public/main.profile.dart.js
vendored
17733
public/main.profile.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -101,18 +101,7 @@
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@if($invoice->subscription && $invoice->subscription?->allow_cancellation)
|
||||
{{-- INV2-591 --}}
|
||||
{{-- @if(false) --}}
|
||||
@if($invoice->subscription && $invoice->subscription?->allow_cancellation && $invoice->status_id == 2)
|
||||
<div class="bg-white shadow sm:rounded-lg mt-4">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="sm:flex sm:items-start sm:justify-between">
|
||||
|
@ -73,7 +73,7 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($invoice->subscription && $invoice->subscription?->allow_cancellation)
|
||||
@if($invoice->subscription && $invoice->subscription?->allow_cancellation && $invoice->status_id == 2)
|
||||
<div class="bg-white shadow sm:rounded-lg mt-4">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<div class="sm:flex sm:items-start sm:justify-between">
|
||||
@ -92,7 +92,7 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if($invoice->subscription && $invoice->subscription->allow_plan_changes)
|
||||
@if($invoice->subscription && $invoice->subscription->allow_plan_changes && count($invoice->subscription->service()->getPlans()) > 0)
|
||||
<div class="bg-white shadow overflow-hidden px-4 py-5 lg:rounded-lg mt-4">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">{{ ctrans('texts.change_plan') }}</h3>
|
||||
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500">{{ ctrans('texts.change_plan_description') }}</p>
|
||||
|
@ -47,6 +47,44 @@ class ExpenseApiTest extends TestCase
|
||||
Model::reguard();
|
||||
}
|
||||
|
||||
public function testExpensePutWithVendorStatus()
|
||||
{
|
||||
|
||||
|
||||
$data =
|
||||
[
|
||||
'vendor_id' => $this->vendor->hashed_id,
|
||||
'amount' => 10,
|
||||
'date' => '2021-10-01',
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson('/api/v1/expenses', $data);
|
||||
|
||||
$arr = $response->json();
|
||||
$response->assertStatus(200);
|
||||
|
||||
|
||||
$this->assertEquals($this->vendor->hashed_id, $arr['data']['vendor_id']);
|
||||
|
||||
$data = [
|
||||
'payment_date' => now()->format('Y-m-d')
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->putJson('/api/v1/expenses/'.$arr['data']['id'], $data);
|
||||
|
||||
$arr = $response->json();
|
||||
$response->assertStatus(200);
|
||||
|
||||
$this->assertEquals($this->vendor->hashed_id, $arr['data']['vendor_id']);
|
||||
|
||||
}
|
||||
|
||||
public function testTransactionIdClearedOnDelete()
|
||||
{
|
||||
$bi = BankIntegration::factory()->create([
|
||||
|
@ -47,6 +47,117 @@ class ProjectApiTest extends TestCase
|
||||
Model::reguard();
|
||||
}
|
||||
|
||||
public function testCreateProjectWithNullTaskRate()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'name' => 'howdy',
|
||||
'task_rate' => null,
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson("/api/v1/projects", $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals(0, $arr['data']['task_rate']);
|
||||
|
||||
}
|
||||
|
||||
public function testCreateProjectWithNullTaskRate2()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'name' => 'howdy',
|
||||
'task_rate' => "A",
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson("/api/v1/projects", $data);
|
||||
|
||||
$response->assertStatus(422);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testCreateProjectWithNullTaskRate3()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'name' => 'howdy',
|
||||
'task_rate' => "10",
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson("/api/v1/projects", $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals(10, $arr['data']['task_rate']);
|
||||
|
||||
}
|
||||
|
||||
public function testCreateProjectWithNullTaskRate5()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'name' => 'howdy',
|
||||
'task_rate' => "-10",
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson("/api/v1/projects", $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals(0, $arr['data']['task_rate']);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function testCreateProjectWithNullTaskRate4()
|
||||
{
|
||||
|
||||
$data = [
|
||||
'client_id' => $this->client->hashed_id,
|
||||
'name' => 'howdy',
|
||||
'task_rate' => 10,
|
||||
];
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token,
|
||||
])->postJson("/api/v1/projects", $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals(10, $arr['data']['task_rate']);
|
||||
|
||||
}
|
||||
|
||||
public function testProjectIncludesZeroCount()
|
||||
{
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user