Merge branches 'feature-inbound-email-expenses' and 'feature-inbound-email-expenses' of https://github.com/paulwer/invoiceninja; branch 'v5-develop' of https://github.com/invoiceninja/invoiceninja into feature-inbound-email-expenses

This commit is contained in:
paulwer 2024-07-02 20:20:04 +02:00
commit 36745bfabc
79 changed files with 280678 additions and 279416 deletions

View File

@ -48,7 +48,7 @@ jobs:
npm i npm i
npm run build npm run build
cp -r dist/* ../public/ cp -r dist/* ../public/
cp dist/index.html ../resources/views/react/index.blade.php mv dist/index.html ../resources/views/react/index.blade.php
- name: Prepare JS/CSS assets - name: Prepare JS/CSS assets
run: | run: |

View File

@ -207,6 +207,14 @@ class Rule extends BaseRule implements RuleInterface
*/ */
public function override($item): self public function override($item): self
{ {
$this->tax_rate1 = $item->tax_rate1;
$this->tax_name1 = $item->tax_name1;
$this->tax_rate2 = $item->tax_rate2;
$this->tax_name2 = $item->tax_name2;
$this->tax_rate3 = $item->tax_rate3;
$this->tax_name3 = $item->tax_name3;
return $this; return $this;
} }

View File

@ -241,7 +241,7 @@ class CreditExport extends BaseExport
} }
if (in_array('credit.user_id', $this->input['report_keys'])) { if (in_array('credit.user_id', $this->input['report_keys'])) {
$entity['credit.user_id'] = $credit->user ? $credit->user->present()->name() : ''; $entity['credit.user_id'] = $credit->user ? $credit->user->present()->name() : ''; //@phpstan-ignore-line
} }
return $entity; return $entity;

View File

@ -85,7 +85,7 @@ class ExpenseExport extends BaseExport
->where('company_id', $this->company->id); ->where('company_id', $this->company->id);
if(!$this->input['include_deleted'] ?? false) { if(!$this->input['include_deleted'] ?? false) { // @phpstan-ignore-line
$query->where('is_deleted', 0); $query->where('is_deleted', 0);
} }
@ -259,10 +259,17 @@ class ExpenseExport extends BaseExport
{ {
$precision = $expense->currency->precision ?? 2; $precision = $expense->currency->precision ?? 2;
$entity['expense.net_amount'] = round($expense->amount, $precision);
if($expense->calculate_tax_by_amount) { if($expense->calculate_tax_by_amount) {
$total_tax_amount = round($expense->tax_amount1 + $expense->tax_amount2 + $expense->tax_amount3, $precision); $total_tax_amount = round($expense->tax_amount1 + $expense->tax_amount2 + $expense->tax_amount3, $precision);
if($expense->uses_inclusive_taxes) {
$entity['expense.net_amount'] = round($expense->amount, $precision) - $total_tax_amount;
}
else {
$entity['expense.net_amount'] = round($expense->amount, $precision);
}
} else { } else {
if($expense->uses_inclusive_taxes) { if($expense->uses_inclusive_taxes) {

View File

@ -63,7 +63,7 @@ class InvoiceExport extends BaseExport
->where('company_id', $this->company->id); ->where('company_id', $this->company->id);
if(!$this->input['include_deleted'] ?? false) { if(!$this->input['include_deleted'] ?? false) {// @phpstan-ignore-line
$query->where('is_deleted', 0); $query->where('is_deleted', 0);
} }
@ -166,7 +166,8 @@ class InvoiceExport extends BaseExport
} }
if (in_array('invoice.user_id', $this->input['report_keys'])) { if (in_array('invoice.user_id', $this->input['report_keys'])) {
$entity['invoice.user_id'] = $invoice->user ? $invoice->user->present()->name() : ''; $entity['invoice.user_id'] = $invoice->user ? $invoice->user->present()->name() : ''; // @phpstan-ignore-line
} }
return $entity; return $entity;

View File

@ -75,7 +75,7 @@ class InvoiceItemExport extends BaseExport
}) })
->where('company_id', $this->company->id); ->where('company_id', $this->company->id);
if(!$this->input['include_deleted'] ?? false) { if(!$this->input['include_deleted'] ?? false) {// @phpstan-ignore-line
$query->where('is_deleted', 0); $query->where('is_deleted', 0);
} }
@ -258,7 +258,7 @@ class InvoiceItemExport extends BaseExport
} }
if (in_array('invoice.user_id', $this->input['report_keys'])) { if (in_array('invoice.user_id', $this->input['report_keys'])) {
$entity['invoice.user_id'] = $invoice->user ? $invoice->user->present()->name() : ''; $entity['invoice.user_id'] = $invoice->user ? $invoice->user->present()->name() : '';// @phpstan-ignore-line
} }
return $entity; return $entity;

View File

@ -63,7 +63,7 @@ class PurchaseOrderExport extends BaseExport
}) })
->where('company_id', $this->company->id); ->where('company_id', $this->company->id);
if(!$this->input['include_deleted'] ?? false) { if(!$this->input['include_deleted'] ?? false) { // @phpstan-ignore-line
$query->where('is_deleted', 0); $query->where('is_deleted', 0);
} }
@ -167,7 +167,8 @@ class PurchaseOrderExport extends BaseExport
} }
if (in_array('purchase_order.user_id', $this->input['report_keys'])) { if (in_array('purchase_order.user_id', $this->input['report_keys'])) {
$entity['purchase_order.user_id'] = $purchase_order->user ? $purchase_order->user->present()->name() : ''; $entity['purchase_order.user_id'] = $purchase_order->user ? $purchase_order->user->present()->name() : ''; // @phpstan-ignore-line
} }
if (in_array('purchase_order.assigned_user_id', $this->input['report_keys'])) { if (in_array('purchase_order.assigned_user_id', $this->input['report_keys'])) {

View File

@ -152,22 +152,22 @@ class InvoiceFilters extends QueryFilters
{ {
return $this->builder->where(function ($query) { return $this->builder->where(function ($query) {
$query->whereIn('status_id', [Invoice::STATUS_PARTIAL, Invoice::STATUS_SENT]) $query->whereIn('invoices.status_id', [Invoice::STATUS_PARTIAL, Invoice::STATUS_SENT])
->where('is_deleted', 0) ->where('invoices.is_deleted', 0)
->where('balance', '>', 0) ->where('invoices.balance', '>', 0)
->where(function ($query) { ->orWhere(function ($query) {
$query->whereNull('due_date') $query->whereNull('invoices.due_date')
->orWhere(function ($q) { ->orWhere(function ($q) {
$q->where('due_date', '>=', now()->startOfDay()->subSecond())->where('partial', 0); $q->where('invoices.due_date', '>=', now()->startOfDay()->subSecond())->where('invoices.partial', 0);
}) })
->orWhere(function ($q) { ->orWhere(function ($q) {
$q->where('partial_due_date', '>=', now()->startOfDay()->subSecond())->where('partial', '>', 0); $q->where('invoices.partial_due_date', '>=', now()->startOfDay()->subSecond())->where('invoices.partial', '>', 0);
}); });
}) })
->orderByRaw('ISNULL(due_date), due_date ' . 'desc') ->orderByRaw('ISNULL(invoices.due_date), invoices.due_date ' . 'desc')
->orderByRaw('ISNULL(partial_due_date), partial_due_date ' . 'desc'); ->orderByRaw('ISNULL(invoices.partial_due_date), invoices.partial_due_date ' . 'desc');
}); });
} }
@ -337,10 +337,10 @@ class InvoiceFilters extends QueryFilters
// return $this->builder->orderByRaw('CAST(number AS UNSIGNED), number ' . $dir); // return $this->builder->orderByRaw('CAST(number AS UNSIGNED), number ' . $dir);
// return $this->builder->orderByRaw("number REGEXP '^[A-Za-z]+$',CAST(number as SIGNED INTEGER),CAST(REPLACE(number,'-','')AS SIGNED INTEGER) ,number"); // return $this->builder->orderByRaw("number REGEXP '^[A-Za-z]+$',CAST(number as SIGNED INTEGER),CAST(REPLACE(number,'-','')AS SIGNED INTEGER) ,number");
// return $this->builder->orderByRaw('ABS(number) ' . $dir); // return $this->builder->orderByRaw('ABS(number) ' . $dir);
return $this->builder->orderByRaw("REGEXP_REPLACE(number,'[^0-9]+','')+0 " . $dir); return $this->builder->orderByRaw("REGEXP_REPLACE(invoices.number,'[^0-9]+','')+0 " . $dir);
} }
return $this->builder->orderBy($sort_col[0], $dir); return $this->builder->orderBy("{$this->builder->getQuery()->from}.".$sort_col[0], $dir);
} }
/** /**

View File

@ -937,7 +937,9 @@ class BaseController extends Controller
} elseif (in_array($this->entity_type, [Design::class, GroupSetting::class, PaymentTerm::class, TaskStatus::class])) { } elseif (in_array($this->entity_type, [Design::class, GroupSetting::class, PaymentTerm::class, TaskStatus::class])) {
// nlog($this->entity_type); // nlog($this->entity_type);
} else { } else {
$query->where('user_id', '=', $user->id)->orWhere('assigned_user_id', $user->id); $query->where(function ($q) use ($user){ //grouping these together improves query performance significantly)
$q->where('user_id', '=', $user->id)->orWhere('assigned_user_id', $user->id);
});
} }
} }

View File

@ -11,8 +11,9 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Requests\Chart\ShowChartRequest;
use App\Services\Chart\ChartService; use App\Services\Chart\ChartService;
use App\Http\Requests\Chart\ShowChartRequest;
use App\Http\Requests\Chart\ShowCalculatedFieldRequest;
class ChartController extends BaseController class ChartController extends BaseController
{ {
@ -65,5 +66,15 @@ class ChartController extends BaseController
return response()->json($cs->chart_summary($request->input('start_date'), $request->input('end_date')), 200); return response()->json($cs->chart_summary($request->input('start_date'), $request->input('end_date')), 200);
} }
public function calculatedField(ShowCalculatedFieldRequest $request)
{
/** @var \App\Models\User auth()->user() */
$user = auth()->user();
$cs = new ChartService($user->company(), $user, $user->isAdmin());
$result = $cs->getCalculatedField($request->all());
return response()->json($result, 200);
}
} }

View File

@ -429,7 +429,7 @@ class ClientController extends BaseController
/** @var ?\Postmark\Models\DynamicResponseModel $response */ /** @var ?\Postmark\Models\DynamicResponseModel $response */
$response = $postmark->activateBounce((int)$bounce_id); $response = $postmark->activateBounce((int)$bounce_id);
if($response && $response?->Message == 'OK' && !$response->Bounce->Inactive && $response->Bounce->Email) { if($response && $response?->Message == 'OK' && !$response->Bounce->Inactive && $response->Bounce->Email) { // @phpstan-ignore-line
$email = $response->Bounce->Email; $email = $response->Bounce->Email;
//remove email from quarantine. //@TODO //remove email from quarantine. //@TODO

View File

@ -57,8 +57,9 @@ class ContactHashLoginController extends Controller
return render('generic.error', [ return render('generic.error', [
'title' => session()->get('title'), 'title' => session()->get('title'),
'notification' => session()->get('notification'), 'notification' => session()->get('notification'),
'account' => auth()->guard('contact')?->user()?->user?->account, 'account' => auth()->guard('contact')?->user()?->user?->account,// @phpstan-ignore-line
'company' => auth()->guard('contact')?->user()?->user?->company 'company' => auth()->guard('contact')?->user()?->user?->company // @phpstan-ignore-line
]); ]);
} }

View File

@ -36,21 +36,16 @@ class ReportExportController extends BaseController
return response()->json(['message' => 'Still working.....'], 409); return response()->json(['message' => 'Still working.....'], 409);
} }
if($report) { Cache::forget($hash);
Cache::forget($hash); $headers = [
'Content-Disposition' => 'attachment',
$headers = [ 'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment', ];
'Content-Type' => 'text/csv',
];
return response()->streamDownload(function () use ($report) {
echo $report;
}, $this->filename, $headers);
}
return response()->streamDownload(function () use ($report) {
echo $report;
}, $this->filename, $headers);
} }
} }

View File

@ -34,12 +34,9 @@ class ReportPreviewController extends BaseController
return response()->json(['message' => 'Still working.....'], 409); return response()->json(['message' => 'Still working.....'], 409);
} }
if($report) { Cache::forget($hash);
Cache::forget($hash); return response()->json($report, 200);
return response()->json($report, 200);
}
} }

View File

@ -35,6 +35,7 @@ class SelfUpdateController extends BaseController
'bootstrap/cache/services.php', 'bootstrap/cache/services.php',
'bootstrap/cache/routes-v7.php', 'bootstrap/cache/routes-v7.php',
'bootstrap/cache/livewire-components.php', 'bootstrap/cache/livewire-components.php',
'public/index.html',
]; ];
public function __construct() public function __construct()
@ -114,33 +115,33 @@ class SelfUpdateController extends BaseController
Artisan::call('config:clear'); Artisan::call('config:clear');
Artisan::call('cache:clear'); Artisan::call('cache:clear');
$this->runModelChecks(); // $this->runModelChecks();
nlog('Called Artisan commands'); nlog('Called Artisan commands');
return response()->json(['message' => 'Update completed'], 200); return response()->json(['message' => 'Update completed'], 200);
} }
private function runModelChecks() // private function runModelChecks()
{ // {
Company::query() // Company::query()
->cursor() // ->cursor()
->each(function ($company) { // ->each(function ($company) {
$settings = $company->settings; // $settings = $company->settings;
if(property_exists($settings->pdf_variables, 'purchase_order_details')) { // if(property_exists($settings->pdf_variables, 'purchase_order_details')) {
return; // return;
} // }
$pdf_variables = $settings->pdf_variables; // $pdf_variables = $settings->pdf_variables;
$pdf_variables->purchase_order_details = []; // $pdf_variables->purchase_order_details = [];
$settings->pdf_variables = $pdf_variables; // $settings->pdf_variables = $pdf_variables;
$company->settings = $settings; // $company->settings = $settings;
$company->save(); // $company->save();
}); // });
} // }
private function clearCacheDir() private function clearCacheDir()
{ {
@ -159,7 +160,7 @@ class SelfUpdateController extends BaseController
$directoryIterator = new \RecursiveDirectoryIterator(base_path(), \RecursiveDirectoryIterator::SKIP_DOTS); $directoryIterator = new \RecursiveDirectoryIterator(base_path(), \RecursiveDirectoryIterator::SKIP_DOTS);
foreach (new \RecursiveIteratorIterator($directoryIterator) as $file) { foreach (new \RecursiveIteratorIterator($directoryIterator) as $file) {
if (strpos($file->getPathname(), '.git') !== false) { if (strpos($file->getPathname(), '.git') !== false || strpos($file->getPathname(), 'vendor/') !== false) {
continue; continue;
} }

View File

@ -164,7 +164,7 @@ class TwilioController extends BaseController
return response()->json(['message' => 'Please update your first and/or last name in the User Details before verifying your number.'], 400); return response()->json(['message' => 'Please update your first and/or last name in the User Details before verifying your number.'], 400);
} }
if (!$user->phone || $user->phone == '') { if (!$user->phone || empty($user->phone)) {
return response()->json(['message' => 'User found, but no valid phone number on file, please contact support.'], 400); return response()->json(['message' => 'User found, but no valid phone number on file, please contact support.'], 400);
} }

View File

@ -56,7 +56,8 @@ class SetDomainNameDb
return response()->json($error, 403); return response()->json($error, 403);
} else { } else {
MultiDB::setDb('db-ninja-01'); MultiDB::setDb('db-ninja-01');
nlog('I could not set the DB - defaulting to DB1'); nlog('SetDomainNameDb:: I could not set the DB - defaulting to DB1');
$request->session()->invalidate();
//abort(400, 'Domain not found'); //abort(400, 'Domain not found');
} }
} }
@ -73,7 +74,8 @@ class SetDomainNameDb
return response()->json($error, 403); return response()->json($error, 403);
} else { } else {
MultiDB::setDb('db-ninja-01'); MultiDB::setDb('db-ninja-01');
nlog('I could not set the DB - defaulting to DB1'); nlog('SetDomainNameDb:: I could not set the DB - defaulting to DB1');
$request->session()->invalidate();
} }
} }
} }

View File

@ -0,0 +1,78 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Http\Requests\Chart;
use App\Http\Requests\Request;
use App\Utils\Traits\MakesDates;
class ShowCalculatedFieldRequest extends Request
{
use MakesDates;
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
/**@var \App\Models\User auth()->user */
$user = auth()->user();
return $user->isAdmin() || $user->hasPermission('view_dashboard');
}
public function rules()
{
return [
'date_range' => 'bail|sometimes|string|in:last7_days,last30_days,last365_days,this_month,last_month,this_quarter,last_quarter,this_year,last_year,all_time,custom',
'start_date' => 'bail|sometimes|date',
'end_date' => 'bail|sometimes|date',
'field' => 'required|bail|in:active_invoices, outstanding_invoices, completed_payments, refunded_payments, active_quotes, unapproved_quotes, logged_tasks, invoiced_tasks, paid_tasks, logged_expenses, pending_expenses, invoiced_expenses, invoice_paid_expenses',
'calculation' => 'required|bail|in:sum,avg,count',
'period' => 'required|bail|in:current,previous,total',
'format' => 'sometimes|bail|in:time,money',
];
}
public function prepareForValidation()
{
/**@var \App\Models\User auth()->user */
$user = auth()->user();
$input = $this->all();
if(isset($input['date_range'])) {
$dates = $this->calculateStartAndEndDates($input, $user->company());
$input['start_date'] = $dates[0];
$input['end_date'] = $dates[1];
}
if (! isset($input['start_date'])) {
$input['start_date'] = now()->subDays(20)->format('Y-m-d');
}
if (! isset($input['end_date'])) {
$input['end_date'] = now()->format('Y-m-d');
}
if(isset($input['period']) && $input['period'] == 'previous')
{
$dates = $this->calculatePreviousPeriodStartAndEndDates($input, $user->company());
$input['start_date'] = $dates[0];
$input['end_date'] = $dates[1];
}
$this->replace($input);
}
}

View File

@ -170,7 +170,7 @@ class UpdateClientRequest extends Request
* down to the free plan setting properties which * down to the free plan setting properties which
* are saveable * are saveable
* *
* @param \stdClass $settings * @param mixed $settings
* @return \stdClass $settings * @return \stdClass $settings
*/ */
private function filterSaveableSettings($settings) private function filterSaveableSettings($settings)

View File

@ -18,7 +18,6 @@ class ClientEmailHistoryRequest extends Request
{ {
use MakesHash; use MakesHash;
private string $error_message = '';
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.
* *

View File

@ -20,7 +20,6 @@ class EntityEmailHistoryRequest extends Request
{ {
use MakesHash; use MakesHash;
private string $error_message = '';
private string $entity_plural = ''; private string $entity_plural = '';
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.

View File

@ -126,7 +126,7 @@ class UpdateRecurringInvoiceRequest extends Request
} }
if (isset($input['line_items'])) { if (isset($input['line_items'])) {
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : []; $input['line_items'] = $this->cleanItems($input['line_items']);
$input['amount'] = $this->entityTotalAmount($input['line_items']); $input['amount'] = $this->entityTotalAmount($input['line_items']);
} }

View File

@ -17,7 +17,6 @@ use Illuminate\Auth\Access\AuthorizationException;
class GenericReportRequest extends Request class GenericReportRequest extends Request
{ {
private string $error_message = '';
/** /**
* Determine if the user is authorized to make this request. * Determine if the user is authorized to make this request.

View File

@ -77,7 +77,7 @@ class UpdateUserRequest extends Request
unset($input['oauth_user_token']); unset($input['oauth_user_token']);
} }
if(isset($input['password']) && strlen($input['password'] ?? '') > 1) if(isset($input['password']) && is_string($input['password']))
{ {
$input['password'] = trim($input['password']); $input['password'] = trim($input['password']);
} }

View File

@ -51,7 +51,7 @@ class HasValidPhoneNumber implements Rule
$twilio = new \Twilio\Rest\Client($sid, $token); $twilio = new \Twilio\Rest\Client($sid, $token);
$country = auth()->user()->account?->companies()?->first()?->country(); $country = auth()->user()->account?->companies()?->first()?->country(); //@phpstan-ignore-line
if (!$country || strlen(auth()->user()->phone) < 2) { if (!$country || strlen(auth()->user()->phone) < 2) {
return true; return true;

View File

@ -47,7 +47,7 @@ class InvoiceTransformer extends BaseTransformer
'due_date' => isset($invoice_data['Due Date']) ? $this->parseDate($invoice_data['Due Date']) : null, 'due_date' => isset($invoice_data['Due Date']) ? $this->parseDate($invoice_data['Due Date']) : null,
'po_number' => $this->getString($invoice_data, 'PurchaseOrder'), 'po_number' => $this->getString($invoice_data, 'PurchaseOrder'),
'public_notes' => $this->getString($invoice_data, 'Notes'), 'public_notes' => $this->getString($invoice_data, 'Notes'),
'currency_id' => $this->getCurrencyByCode($invoice_data, 'Currency'), // 'currency_id' => $this->getCurrencyByCode($invoice_data, 'Currency'),
'amount' => $this->getFloat($invoice_data, 'Total'), 'amount' => $this->getFloat($invoice_data, 'Total'),
'balance' => $this->getFloat($invoice_data, 'Balance'), 'balance' => $this->getFloat($invoice_data, 'Balance'),
'status_id' => $invoiceStatusMap[$status = 'status_id' => $invoiceStatusMap[$status =

View File

@ -86,6 +86,9 @@ class UpdateCalculatedFields
foreach(json_decode($task->time_log) as $log) { foreach(json_decode($task->time_log) as $log) {
if(!is_array($log))
continue;
$start_time = $log[0]; $start_time = $log[0];
$end_time = $log[1] == 0 ? time() : $log[1]; $end_time = $log[1] == 0 ? time() : $log[1];

View File

@ -71,7 +71,7 @@ class ReminderJob implements ShouldQueue
->whereHas('company', function ($query) { ->whereHas('company', function ($query) {
$query->where('is_disabled', 0); $query->where('is_disabled', 0);
}) })
->with('invitations')->chunk(50, function ($invoices) { ->with('invitations')->chunk(200, function ($invoices) {
foreach ($invoices as $invoice) { foreach ($invoices as $invoice) {
$this->sendReminderForInvoice($invoice); $this->sendReminderForInvoice($invoice);
} }
@ -99,7 +99,7 @@ class ReminderJob implements ShouldQueue
->whereHas('company', function ($query) { ->whereHas('company', function ($query) {
$query->where('is_disabled', 0); $query->where('is_disabled', 0);
}) })
->with('invitations')->chunk(50, function ($invoices) { ->with('invitations')->chunk(200, function ($invoices) {
foreach ($invoices as $invoice) { foreach ($invoices as $invoice) {
$this->sendReminderForInvoice($invoice); $this->sendReminderForInvoice($invoice);

View File

@ -129,7 +129,7 @@ class BaseModel extends Model
/** @var \App\Models\User $user */ /** @var \App\Models\User $user */
$user = auth()->user(); $user = auth()->user();
$query->where('company_id', $user->companyId()); $query->where("{$query->getQuery()->from}.company_id", $user->companyId());
return $query; return $query;
} }

View File

@ -107,7 +107,6 @@ use Laracasts\Presenter\PresentableTrait;
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Document> $documents
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Backup> $history
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations * @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\QuoteInvitation> $invitations
* @method static \Illuminate\Database\Eloquent\Builder|BaseModel company()
* @mixin \Eloquent * @mixin \Eloquent
* @mixin \Illuminate\Database\Eloquent\Builder * @mixin \Illuminate\Database\Eloquent\Builder
*/ */

View File

@ -186,7 +186,7 @@ class Task extends BaseModel
} }
if($this->status) { if($this->status) {
return '<h5><span class="badge badge-primary">' . $this->status?->name ?? ''; return '<h5><span class="badge badge-primary">' . $this->status?->name ?? ''; //@phpstan-ignore-line
} }
return ''; return '';

View File

@ -136,16 +136,16 @@ class EntityViewedNotification extends Notification
// return $data; // return $data;
// } // }
private function buildSubject() // private function buildSubject()
{ // {
$subject = ctrans( // $subject = ctrans(
"texts.notification_{$this->entity_name}_viewed_subject", // "texts.notification_{$this->entity_name}_viewed_subject",
[ // [
'client' => $this->contact->present()->name(), // 'client' => $this->contact->present()->name(),
$this->entity_name => $this->entity->number, // $this->entity_name => $this->entity->number,
] // ]
); // );
return $subject; // return $subject;
} // }
} }

View File

@ -559,7 +559,7 @@ class BaseDriver extends AbstractPaymentDriver
$error = 'Payment Aborted'; $error = 'Payment Aborted';
} }
if (! is_null($this->payment_hash)) { if (! is_null($this->payment_hash)) { //@phpstan-ignore-line
$this->unWindGatewayFees($this->payment_hash); $this->unWindGatewayFees($this->payment_hash);
} }
@ -830,7 +830,7 @@ class BaseDriver extends AbstractPaymentDriver
} }
$invoices_string = \implode(', ', collect($this->payment_hash->invoices())->pluck('invoice_number')->toArray()) ?: null; $invoices_string = \implode(', ', collect($this->payment_hash->invoices())->pluck('invoice_number')->toArray()) ?: null;
$amount = Number::formatMoney($this->payment_hash?->amount_with_fee() ?? 0, $this->client); $amount = Number::formatMoney($this->payment_hash?->amount_with_fee() ?? 0, $this->client); // @phpstan-ignore-line
if($abbreviated && $invoices_string) { if($abbreviated && $invoices_string) {
return $invoices_string; return $invoices_string;

View File

@ -40,7 +40,7 @@ class Webhook
$error_details = $e->error_details; $error_details = $e->error_details;
nlog($error_details); nlog($error_details);
$http_status_code = isset($e->http_metadata) ? $e->http_metadata->getStatusCode() : null; $http_status_code = isset($e->http_metadata) ? $e->http_metadata->getStatusCode() : null; //@phpstan-ignore-line
} catch (CheckoutAuthorizationException $e) { } catch (CheckoutAuthorizationException $e) {
// Bad Invalid authorization // Bad Invalid authorization
} }

View File

@ -78,7 +78,7 @@ class CreditCard
$this->logResponse($response); $this->logResponse($response);
throw new PaymentFailed($response_status['message'] ?? 'Unknown response from gateway, please contact you merchant.', 400); throw new PaymentFailed($response_status['message'] ?? 'Unknown response from gateway, please contact you merchant.', 400); //@phpstan-ignore-line
} }
//success //success
@ -135,7 +135,7 @@ class CreditCard
$invoice_numbers = ''; $invoice_numbers = '';
if ($this->eway_driver->payment_hash->data) { if ($this->eway_driver->payment_hash->data) {
$invoice_numbers = collect($this->eway_driver->payment_hash->data->invoices)->pluck('invoice_number')->implode(','); $invoice_numbers = collect($this->eway_driver->payment_hash->data->invoices)->pluck('invoice_number')->implode(','); //@phpstan-ignore-line
} }
$amount = array_sum(array_column($this->eway_driver->payment_hash->invoices(), 'amount')) + $this->eway_driver->payment_hash->fee_total; $amount = array_sum(array_column($this->eway_driver->payment_hash->invoices(), 'amount')) + $this->eway_driver->payment_hash->fee_total;

View File

@ -173,7 +173,7 @@ class ACH implements MethodInterface
$description = "Amount {$request->amount} from client {$this->go_cardless->client->present()->name()}"; $description = "Amount {$request->amount} from client {$this->go_cardless->client->present()->name()}";
} }
$amount = $this->go_cardless->convertToGoCardlessAmount($this->go_cardless->payment_hash?->amount_with_fee(), $this->go_cardless->client->currency()->precision); $amount = $this->go_cardless->convertToGoCardlessAmount($this->go_cardless->payment_hash?->amount_with_fee(), $this->go_cardless->client->currency()->precision); //@phpstan-ignore-line
try { try {
$payment = $this->go_cardless->gateway->payments()->create([ $payment = $this->go_cardless->gateway->payments()->create([

View File

@ -156,7 +156,7 @@ class MolliePaymentDriver extends BaseDriver
return [ return [
'transaction_reference' => $refund->id, 'transaction_reference' => $refund->id,
'transaction_response' => json_encode($refund), 'transaction_response' => json_encode($refund),
'success' => $refund->status === 'refunded' ? true : false, 'success' => $refund->status === 'refunded' ? true : false, //@phpstan-ignore-line
'description' => $refund->description, 'description' => $refund->description,
'code' => 200, 'code' => 200,
]; ];

View File

@ -489,7 +489,7 @@ class StripePaymentDriver extends BaseDriver
{ {
$customer = Customer::retrieve($customer_id, $this->stripe_connect_auth); $customer = Customer::retrieve($customer_id, $this->stripe_connect_auth);
return $customer ?? null; return $customer ?? null; // @phpstan-ignore-line
} }
/** /**

View File

@ -261,7 +261,7 @@ class TaskRepository extends BaseRepository
public function roundTimeLog(int $start_time, int $end_time): int public function roundTimeLog(int $start_time, int $end_time): int
{ {
if($this->task_round_to_nearest == 1 || $end_time == 0) { if(in_array($this->task_round_to_nearest, [0,1]) || $end_time == 0) {
return $end_time; return $end_time;
} }

View File

@ -0,0 +1,173 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\Chart;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Quote;
/**
* Class ChartCalculations.
*/
trait ChartCalculations
{
public function getActiveInvoices($data): int|float
{
$result = 0;
$q = Invoice::query()
->withTrashed()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->whereIn('status_id', [2,3,4]);
if(in_array($data['period'],['current,previous']))
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
match ($data['calculation']) {
'sum' => $result = $q->sum('amount'),
'avg' => $result = $q->avg('amount'),
'count' => $result = $q->count(),
default => $result = 0,
};
return $result;
}
public function getOutstandingInvoices($data): int|float
{
$result = 0;
$q = Invoice::query()
->withTrashed()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->whereIn('status_id', [2,3]);
if(in_array($data['period'],['current,previous']))
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
match ($data['calculation']) {
'sum' => $result = $q->sum('balance'),
'avg' => $result = $q->avg('balance'),
'count' => $result = $q->count(),
default => $result = 0,
};
return $result;
}
public function getCompletedPayments($data): int|float
{
$result = 0;
$q = Payment::query()
->withTrashed()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->where('status_id', 4);
if(in_array($data['period'],['current,previous']))
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
match ($data['calculation']) {
'sum' => $result = $q->sum('amount'),
'avg' => $result = $q->avg('amount'),
'count' => $result = $q->count(),
default => $result = 0,
};
return $result;
}
public function getRefundedPayments($data): int|float
{
$result = 0;
$q = Payment::query()
->withTrashed()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->whereIn('status_id', [5,6]);
if(in_array($data['period'],['current,previous']))
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
match ($data['calculation']) {
'sum' => $result = $q->sum('refunded'),
'avg' => $result = $q->avg('refunded'),
'count' => $result = $q->count(),
default => $result = 0,
};
return $result;
}
public function getActiveQuotes($data): int|float
{
$result = 0;
$q = Quote::query()
->withTrashed()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->whereIn('status_id', [2,3])
->where(function ($qq){
$qq->where('due_date', '>=', now()->toDateString())->orWhereNull('due_date');
});
if(in_array($data['period'],['current,previous']))
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
match ($data['calculation']) {
'sum' => $result = $q->sum('refunded'),
'avg' => $result = $q->avg('refunded'),
'count' => $result = $q->count(),
default => $result = 0,
};
return $result;
}
public function getUnapprovedQuotes($data): int|float
{
$result = 0;
$q = Quote::query()
->withTrashed()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->whereIn('status_id', [2])
->where(function ($qq){
$qq->where('due_date', '>=', now()->toDateString())->orWhereNull('due_date');
});
if(in_array($data['period'],['current,previous']))
$q->whereBetween('date', [$data['start_date'], $data['end_date']]);
match ($data['calculation']) {
'sum' => $result = $q->sum('refunded'),
'avg' => $result = $q->avg('refunded'),
'count' => $result = $q->count(),
default => $result = 0,
};
return $result;
}
}

View File

@ -14,12 +14,17 @@ namespace App\Services\Chart;
use App\Models\Client; use App\Models\Client;
use App\Models\Company; use App\Models\Company;
use App\Models\Expense; use App\Models\Expense;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Quote;
use App\Models\Task;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
class ChartService class ChartService
{ {
use ChartQueries; use ChartQueries;
use ChartCalculations;
public function __construct(public Company $company, private User $user, private bool $is_admin) public function __construct(public Company $company, private User $user, private bool $is_admin)
{ {
@ -71,7 +76,7 @@ class ChartService
return $final_currencies; return $final_currencies;
} }
/* Chart Data */ /* Chart Data */
public function chart_summary($start_date, $end_date): array public function chart_summary($start_date, $end_date): array
{ {
@ -207,4 +212,44 @@ class ChartService
return ''; return '';
} }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* calculatedField
*
* @param array $data -
*
* field - list of fields for calculation
* period - current/previous
* calculation - sum/count/avg
*
* date_range - this_month
* or
* start_date - end_date
*/
public function getCalculatedField(array $data)
{
$results = 0;
match($data['field']){
'active_invoices' => $results = $this->getActiveInvoices($data),
'outstanding_invoices' => $results = 0,
'completed_payments' => $results = 0,
'refunded_payments' => $results = 0,
'active_quotes' => $results = 0,
'unapproved_quotes' => $results = 0,
'logged_tasks' => $results = 0,
'invoiced_tasks' => $results = 0,
'paid_tasks' => $results = 0,
'logged_expenses' => $results = 0,
'pending_expenses' => $results = 0,
'invoiced_expenses' => $results = 0,
'invoice_paid_expenses' => $results = 0,
default => $results = 0,
};
return $results;
}
} }

View File

@ -52,8 +52,9 @@ class ZugferdEDocument extends AbstractService
$this->document->getDocumentInformation($documentno, $documenttypecode, $documentdate, $invoiceCurrency, $taxCurrency, $documentname, $documentlanguage, $effectiveSpecifiedPeriod); $this->document->getDocumentInformation($documentno, $documenttypecode, $documentdate, $invoiceCurrency, $taxCurrency, $documentname, $documentlanguage, $effectiveSpecifiedPeriod);
$this->document->getDocumentSummation($grandTotalAmount, $duePayableAmount, $lineTotalAmount, $chargeTotalAmount, $allowanceTotalAmount, $taxBasisTotalAmount, $taxTotalAmount, $roundingAmount, $totalPrepaidAmount); $this->document->getDocumentSummation($grandTotalAmount, $duePayableAmount, $lineTotalAmount, $chargeTotalAmount, $allowanceTotalAmount, $taxBasisTotalAmount, $taxTotalAmount, $roundingAmount, $totalPrepaidAmount);
$expense = Expense::where('amount', $grandTotalAmount)->where("transaction_reference", $documentno)->whereDate("date", $documentdate)->first(); /** @var \App\Models\Expense $expense */
if (empty($expense)) { $expense = Expense::where("company_id", $user->company()->id)->where('amount', $grandTotalAmount)->where("transaction_reference", $documentno)->whereDate("date", $documentdate)->first();
if (!$expense) {
// The document does not exist as an expense // The document does not exist as an expense
// Handle accordingly // Handle accordingly
$visualizer = new ZugferdVisualizer($this->document); $visualizer = new ZugferdVisualizer($this->document);
@ -65,8 +66,6 @@ class ZugferdEDocument extends AbstractService
$expense = ExpenseFactory::create($user->company()->id, $user->id); $expense = ExpenseFactory::create($user->company()->id, $user->id);
$expense->date = $documentdate; $expense->date = $documentdate;
$expense->user_id = $user->id;
$expense->company_id = $user->company->id;
$expense->public_notes = $documentno; $expense->public_notes = $documentno;
$expense->currency_id = Currency::whereCode($invoiceCurrency)->first()?->id || $user->company->settings->currency_id; $expense->currency_id = Currency::whereCode($invoiceCurrency)->first()?->id || $user->company->settings->currency_id;
$expense->save(); $expense->save();
@ -75,7 +74,7 @@ class ZugferdEDocument extends AbstractService
if ($this->file->getExtension() == "xml") if ($this->file->getExtension() == "xml")
array_push($documents, TempFile::UploadedFileFromRaw($visualizer->renderPdf(), $documentno . "_visualiser.pdf", "application/pdf")); array_push($documents, TempFile::UploadedFileFromRaw($visualizer->renderPdf(), $documentno . "_visualiser.pdf", "application/pdf"));
$this->saveDocuments($documents, $expense); $this->saveDocuments($documents, $expense);
$expense->saveQuietly(); $expense->save();
if ($taxCurrency && $taxCurrency != $invoiceCurrency) { if ($taxCurrency && $taxCurrency != $invoiceCurrency) {
$expense->private_notes = ctrans("texts.tax_currency_mismatch"); $expense->private_notes = ctrans("texts.tax_currency_mismatch");

View File

@ -44,7 +44,7 @@ use CleverIt\UBL\Invoice\FatturaPA\common\FatturaElettronicaHeader;
*/ */
class FatturaPA extends AbstractService class FatturaPA extends AbstractService
{ {
private $xml; // private $xml;
//urn:cen.eu:en16931:2017#compliant#urn:fatturapa.gov.it:CIUS-IT:2.0.0 //urn:cen.eu:en16931:2017#compliant#urn:fatturapa.gov.it:CIUS-IT:2.0.0
//<cbc:EndpointID schemeID=" 0201 ">UFF001</cbc:EndpointID> //<cbc:EndpointID schemeID=" 0201 ">UFF001</cbc:EndpointID>

View File

@ -205,7 +205,7 @@ class AdminEmail implements ShouldQueue
$this->entityEmailFailed($message); $this->entityEmailFailed($message);
/* Don't send postmark failures to Sentry */ /* Don't send postmark failures to Sentry */
if (Ninja::isHosted() && (!$e instanceof ClientException)) { if (Ninja::isHosted() && (!$e instanceof ClientException)) { //@phpstan-ignore-line
app('sentry')->captureException($e); app('sentry')->captureException($e);
} }
} }

View File

@ -33,9 +33,6 @@ class RefundPayment
public function __construct(public Payment $payment, public array $refund_data) public function __construct(public Payment $payment, public array $refund_data)
{ {
$this->gateway_refund_status = false;
$this->activity_repository = new ActivityRepository();
} }
public function run() public function run()

View File

@ -1665,7 +1665,7 @@ class PdfBuilder
if ($child['element'] !== 'script') { if ($child['element'] !== 'script') {
if ($this->service->company->markdown_enabled && array_key_exists('content', $child)) { if ($this->service->company->markdown_enabled && array_key_exists('content', $child)) {
$child['content'] = str_replace('<br>', "\r", ($child['content'] ?? '')); $child['content'] = str_replace('<br>', "\r", ($child['content'] ?? ''));
$child['content'] = $this->commonmark->convert($child['content'] ?? ''); $child['content'] = $this->commonmark->convert($child['content'] ?? ''); //@phpstan-ignore-line
} }
} }

View File

@ -469,7 +469,7 @@ class PdfMock
'$country_2' => 'AF', '$country_2' => 'AF',
'$firstName' => 'Benedict', '$firstName' => 'Benedict',
'$user.name' => 'Derrick Monahan DDS Erna Wunsch', '$user.name' => 'Derrick Monahan DDS Erna Wunsch',
'$font_name' => isset($this->settings?->primary_font) ? $this->settings?->primary_font : 'Roboto', '$font_name' => isset($this->settings?->primary_font) ? $this->settings?->primary_font : 'Roboto', //@phpstan-ignore-line
'$auto_bill' => 'This invoice will automatically be billed to your credit card on file on the due date.', '$auto_bill' => 'This invoice will automatically be billed to your credit card on file on the due date.',
'$payments' => '', '$payments' => '',
'$task.tax' => '', '$task.tax' => '',

View File

@ -287,7 +287,7 @@ class Design extends BaseDesign
{ {
$elements = []; $elements = [];
if (!$this->client) { if (!$this->client) {//@phpstan-ignore-line
return $elements; return $elements;
} }
@ -359,7 +359,7 @@ class Design extends BaseDesign
$variables = $this->context['pdf_variables']['credit_details']; $variables = $this->context['pdf_variables']['credit_details'];
} }
if ($this->vendor) { if ($this->vendor) { //@phpstan-ignore-line
$variables = $this->context['pdf_variables']['purchase_order_details']; $variables = $this->context['pdf_variables']['purchase_order_details'];
} }

View File

@ -64,9 +64,9 @@ trait DesignHelpers
$this->document(); $this->document();
$this->settings_object = $this->vendor ? $this->vendor->company : $this->client; $this->settings_object = $this->vendor ? $this->vendor->company : $this->client; //@phpstan-ignore-line
$this->company = $this->vendor ? $this->vendor->company : $this->client->company; $this->company = $this->vendor ? $this->vendor->company : $this->client->company; //@phpstan-ignore-line
return $this; return $this;
} }
@ -387,7 +387,7 @@ trait DesignHelpers
return ''; return '';
} }
if ($this->client->company->custom_fields && ! property_exists($this->client->company->custom_fields, $field)) { if ($this->client->company->custom_fields && ! property_exists($this->client->company->custom_fields, $field)) { //@phpstan-ignore-line
return ''; return '';
} }

View File

@ -27,16 +27,6 @@ class PdfMaker
public $document; public $document;
private $xpath;
private $filters = [
'<![CDATA[' => '',
'<![CDATA[<![CDATA[' => '',
']]]]><![CDATA[>]]>' => '',
']]>' => '',
'<?xml version="1.0" encoding="utf-8" standalone="yes"??>' => '',
];
private $options; private $options;
/** @var CommonMarkConverter */ /** @var CommonMarkConverter */

View File

@ -94,7 +94,7 @@ trait PdfMakerUtilities
if ($child['element'] !== 'script') { if ($child['element'] !== 'script') {
if (array_key_exists('process_markdown', $this->data) && array_key_exists('content', $child) && $this->data['process_markdown']) { if (array_key_exists('process_markdown', $this->data) && array_key_exists('content', $child) && $this->data['process_markdown']) {
$child['content'] = str_replace('<br>', "\r", ($child['content'] ?? '')); $child['content'] = str_replace('<br>', "\r", ($child['content'] ?? ''));
$child['content'] = $this->commonmark->convert($child['content'] ?? ''); $child['content'] = $this->commonmark->convert($child['content'] ?? ''); //@phpstan-ignore-line
} }
} }

View File

@ -41,7 +41,11 @@ class CreateInvitations extends AbstractService
public function run() public function run()
{ {
$contacts = $this->purchase_order?->vendor?->contacts()->get();
if(!$this->purchase_order->vendor)
return $this->purchase_order;
$contacts = $this->purchase_order->vendor->contacts()->get();
if ($contacts->count() == 0) { if ($contacts->count() == 0) {
$this->createBlankContact(); $this->createBlankContact();

View File

@ -11,19 +11,15 @@
namespace App\Services\PurchaseOrder; namespace App\Services\PurchaseOrder;
use App\Models\PurchaseOrder; use App\Models\Vendor;
use App\Models\Webhook; use App\Models\Webhook;
use App\Models\PurchaseOrder;
class MarkSent class MarkSent
{ {
private $vendor;
public function __construct(public Vendor $vendor, public PurchaseOrder $purchase_order)
private $purchase_order;
public function __construct($vendor, $purchase_order)
{ {
$this->vendor = $vendor;
$this->purchase_order = $purchase_order;
} }
public function run() public function run()

View File

@ -1,40 +0,0 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Services\Quote;
use App\Events\Quote\QuoteWasMarkedApproved;
use App\Models\Quote;
use App\Utils\Ninja;
class MarkApproved
{
private $client;
public function __construct($client)
{
$this->client = $client;
}
public function run($quote)
{
/* Return immediately if status is not draft */
if ($quote->status_id != Quote::STATUS_SENT) {
return $quote;
}
$quote->service()->setStatus(Quote::STATUS_APPROVED)->applyNumber()->save();
event(new QuoteWasMarkedApproved($quote, $quote->company, Ninja::eventVars()));
return $quote;
}
}

View File

@ -171,39 +171,39 @@ class UpdateReminder extends AbstractService
return $this->quote; return $this->quote;
} }
private function addTimeInterval($date, $endless_reminder_frequency_id): ?Carbon // private function addTimeInterval($date, $endless_reminder_frequency_id): ?Carbon
{ // {
if (! $date) { // if (! $date) {
return null; // return null;
} // }
switch ($endless_reminder_frequency_id) { // switch ($endless_reminder_frequency_id) {
case RecurringInvoice::FREQUENCY_DAILY: // case RecurringInvoice::FREQUENCY_DAILY:
return Carbon::parse($date)->addDay()->startOfDay(); // return Carbon::parse($date)->addDay()->startOfDay();
case RecurringInvoice::FREQUENCY_WEEKLY: // case RecurringInvoice::FREQUENCY_WEEKLY:
return Carbon::parse($date)->addWeek()->startOfDay(); // return Carbon::parse($date)->addWeek()->startOfDay();
case RecurringInvoice::FREQUENCY_TWO_WEEKS: // case RecurringInvoice::FREQUENCY_TWO_WEEKS:
return Carbon::parse($date)->addWeeks(2)->startOfDay(); // return Carbon::parse($date)->addWeeks(2)->startOfDay();
case RecurringInvoice::FREQUENCY_FOUR_WEEKS: // case RecurringInvoice::FREQUENCY_FOUR_WEEKS:
return Carbon::parse($date)->addWeeks(4)->startOfDay(); // return Carbon::parse($date)->addWeeks(4)->startOfDay();
case RecurringInvoice::FREQUENCY_MONTHLY: // case RecurringInvoice::FREQUENCY_MONTHLY:
return Carbon::parse($date)->addMonthNoOverflow()->startOfDay(); // return Carbon::parse($date)->addMonthNoOverflow()->startOfDay();
case RecurringInvoice::FREQUENCY_TWO_MONTHS: // case RecurringInvoice::FREQUENCY_TWO_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(2)->startOfDay(); // return Carbon::parse($date)->addMonthsNoOverflow(2)->startOfDay();
case RecurringInvoice::FREQUENCY_THREE_MONTHS: // case RecurringInvoice::FREQUENCY_THREE_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(3)->startOfDay(); // return Carbon::parse($date)->addMonthsNoOverflow(3)->startOfDay();
case RecurringInvoice::FREQUENCY_FOUR_MONTHS: // case RecurringInvoice::FREQUENCY_FOUR_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(4)->startOfDay(); // return Carbon::parse($date)->addMonthsNoOverflow(4)->startOfDay();
case RecurringInvoice::FREQUENCY_SIX_MONTHS: // case RecurringInvoice::FREQUENCY_SIX_MONTHS:
return Carbon::parse($date)->addMonthsNoOverflow(6)->startOfDay(); // return Carbon::parse($date)->addMonthsNoOverflow(6)->startOfDay();
case RecurringInvoice::FREQUENCY_ANNUALLY: // case RecurringInvoice::FREQUENCY_ANNUALLY:
return Carbon::parse($date)->addYear()->startOfDay(); // return Carbon::parse($date)->addYear()->startOfDay();
case RecurringInvoice::FREQUENCY_TWO_YEARS: // case RecurringInvoice::FREQUENCY_TWO_YEARS:
return Carbon::parse($date)->addYears(2)->startOfDay(); // return Carbon::parse($date)->addYears(2)->startOfDay();
case RecurringInvoice::FREQUENCY_THREE_YEARS: // case RecurringInvoice::FREQUENCY_THREE_YEARS:
return Carbon::parse($date)->addYears(3)->startOfDay(); // return Carbon::parse($date)->addYears(3)->startOfDay();
default: // default:
return null; // return null;
} // }
} // }
} }

View File

@ -33,7 +33,7 @@ class UpdatePrice extends AbstractService
->where('is_deleted', 0) ->where('is_deleted', 0)
->first(); ->first();
if ($product) { if ($product) { //@phpstan-ignore-line
$line_items[$key]->cost = floatval($product->price); $line_items[$key]->cost = floatval($product->price);
} }
} }

View File

@ -183,17 +183,17 @@ class ProfitLoss
return $this; return $this;
} }
private function getForeignIncome(): array // private function getForeignIncome(): array
{ // {
return $this->foreign_income; // return $this->foreign_income;
} // }
private function filterPaymentIncome() // private function filterPaymentIncome()
{ // {
$payments = $this->paymentIncome(); // $payments = $this->paymentIncome();
return $this; // return $this;
} // }
/* /*
//returns an array of objects //returns an array of objects
@ -427,26 +427,26 @@ class ProfitLoss
+"payments_converted": "12260.870000000000", +"payments_converted": "12260.870000000000",
+"currency_id": 1, +"currency_id": 1,
*/ */
private function paymentIncome() // private function paymentIncome()
{ // {
return \DB::select(' // return \DB::select('
SELECT // SELECT
SUM(coalesce(payments.amount - payments.refunded,0)) as payments, // SUM(coalesce(payments.amount - payments.refunded,0)) as payments,
SUM(coalesce(payments.amount - payments.refunded,0)) * IFNULL(payments.exchange_rate ,1) as payments_converted, // SUM(coalesce(payments.amount - payments.refunded,0)) * IFNULL(payments.exchange_rate ,1) as payments_converted,
payments.currency_id as currency_id // payments.currency_id as currency_id
FROM clients // FROM clients
INNER JOIN // INNER JOIN
payments ON // payments ON
clients.id=payments.client_id // clients.id=payments.client_id
WHERE payments.status_id IN (1,4,5,6) // WHERE payments.status_id IN (1,4,5,6)
AND clients.is_deleted = false // AND clients.is_deleted = false
AND payments.is_deleted = false // AND payments.is_deleted = false
AND payments.company_id = :company_id // AND payments.company_id = :company_id
AND (payments.date BETWEEN :start_date AND :end_date) // AND (payments.date BETWEEN :start_date AND :end_date)
GROUP BY currency_id // GROUP BY currency_id
ORDER BY currency_id; // ORDER BY currency_id;
', ['company_id' => $this->company->id, 'start_date' => $this->start_date, 'end_date' => $this->end_date]); // ', ['company_id' => $this->company->id, 'start_date' => $this->start_date, 'end_date' => $this->end_date]);
} // }
private function expenseData() private function expenseData()
{ {
@ -544,18 +544,18 @@ class ProfitLoss
return round(($amount * $exchange_rate), 2); return round(($amount * $exchange_rate), 2);
} }
private function expenseCalcWithTax() // private function expenseCalcWithTax()
{ // {
return \DB::select(' // return \DB::select('
SELECT sum(expenses.amount) as amount, // SELECT sum(expenses.amount) as amount,
IFNULL(expenses.currency_id, :company_currency) as currency_id // IFNULL(expenses.currency_id, :company_currency) as currency_id
FROM expenses // FROM expenses
WHERE expenses.is_deleted = 0 // WHERE expenses.is_deleted = 0
AND expenses.company_id = :company_id // AND expenses.company_id = :company_id
AND (expenses.date BETWEEN :start_date AND :end_date) // AND (expenses.date BETWEEN :start_date AND :end_date)
GROUP BY currency_id // GROUP BY currency_id
', ['company_currency' => $this->company->settings->currency_id, 'company_id' => $this->company->id, 'start_date' => $this->start_date, 'end_date' => $this->end_date]); // ', ['company_currency' => $this->company->settings->currency_id, 'company_id' => $this->company->id, 'start_date' => $this->start_date, 'end_date' => $this->end_date]);
} // }
private function setBillingReportType() private function setBillingReportType()
{ {

View File

@ -23,8 +23,6 @@ class EmailStatementService
use MakesHash; use MakesHash;
use MakesDates; use MakesDates;
private Client $client;
public function __construct(public Scheduler $scheduler) public function __construct(public Scheduler $scheduler)
{ {
} }
@ -45,9 +43,6 @@ class EmailStatementService
$query->cursor() $query->cursor()
->each(function ($_client) { ->each(function ($_client) {
/**@var \App\Models\Client $_client */
$this->client = $_client;
//work out the date range //work out the date range
$statement_properties = $this->calculateStatementProperties($_client); $statement_properties = $this->calculateStatementProperties($_client);

View File

@ -102,7 +102,7 @@ class SubscriptionCalculator
$line_item->quantity = (float) $item['quantity']; $line_item->quantity = (float) $item['quantity'];
$line_item->cost = (float) $item['product']['price']; $line_item->cost = (float) $item['product']['price'];
$line_item->notes = $item['product']['notes']; $line_item->notes = $item['product']['notes'];
$line_item->tax_id = (string)$item['product']['tax_id'] ?? '1'; $line_item->tax_id = (string)$item['product']['tax_id'] ?? '1'; //@phpstan-ignore-line
$items[] = $line_item; $items[] = $line_item;
} }

View File

@ -145,7 +145,7 @@ class SubscriptionService
/* 06-04-2022 */ /* 06-04-2022 */
/* We may not be in a state where the user is present */ /* We may not be in a state where the user is present */
if (auth()->guard('contact')) { if (auth()->guard('contact')->user()) {
return $this->handleRedirect('/client/invoices/'.$this->encodePrimaryKey($payment_hash->fee_invoice_id)); return $this->handleRedirect('/client/invoices/'.$this->encodePrimaryKey($payment_hash->fee_invoice_id));
} }
} }
@ -200,7 +200,7 @@ class SubscriptionService
$license->first_name = $contact ? $contact->first_name : ' '; $license->first_name = $contact ? $contact->first_name : ' ';
$license->last_name = $contact ? $contact->last_name : ' '; $license->last_name = $contact ? $contact->last_name : ' ';
$license->is_claimed = 1; $license->is_claimed = 1;
$license->transaction_reference = $payment_hash?->payment?->transaction_reference ?: ' '; $license->transaction_reference = $payment_hash?->payment?->transaction_reference ?: ' '; //@phpstan-ignore-line
$license->product_id = self::WHITE_LABEL; $license->product_id = self::WHITE_LABEL;
$license->recurring_invoice_id = $recurring_invoice->id; $license->recurring_invoice_id = $recurring_invoice->id;

View File

@ -1327,6 +1327,7 @@ class TemplateService
{ {
$entity_string = ''; $entity_string = '';
//@phpstan-ignore-next-line
match($this->entity) { match($this->entity) {
($this->entity instanceof Invoice) => $entity_string = 'invoice', ($this->entity instanceof Invoice) => $entity_string = 'invoice',
($this->entity instanceof Quote) => $entity_string = 'quote', ($this->entity instanceof Quote) => $entity_string = 'quote',

View File

@ -85,6 +85,7 @@ class SystemHealth
'file_permissions' => (string) self::checkFileSystem(), 'file_permissions' => (string) self::checkFileSystem(),
'exchange_rate_api_not_configured' => (bool)self::checkCurrencySanity(), 'exchange_rate_api_not_configured' => (bool)self::checkCurrencySanity(),
'api_version' => (string) config('ninja.app_version'), 'api_version' => (string) config('ninja.app_version'),
'is_docker' => (bool) config('ninja.is_docker'),
]; ];
} }

View File

@ -146,7 +146,6 @@ trait MakesDates
} }
return match ($data['date_range']) { return match ($data['date_range']) {
EmailStatement::LAST7 => [now()->startOfDay()->subDays(7)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')], EmailStatement::LAST7 => [now()->startOfDay()->subDays(7)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')],
EmailStatement::LAST30 => [now()->startOfDay()->subDays(30)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')], EmailStatement::LAST30 => [now()->startOfDay()->subDays(30)->format('Y-m-d'), now()->startOfDay()->format('Y-m-d')],
@ -162,4 +161,37 @@ trait MakesDates
}; };
} }
public function calculatePreviousPeriodStartAndEndDates(array $data, ?Company $company = null): array
{
//override for financial years
if($data['date_range'] == 'this_year') {
$first_month_of_year = $company ? $company?->first_month_of_year : 1;
$fin_year_start = now()->createFromDate(now()->year, $first_month_of_year, 1);
$fin_year_start->subYearNoOverflow();
if(now()->subYear()->lt($fin_year_start)) {
$fin_year_start->subYearNoOverflow();
}
}
return match ($data['date_range']) {
EmailStatement::LAST7 => [now()->startOfDay()->subDays(14)->format('Y-m-d'), now()->subDays(7)->startOfDay()->format('Y-m-d')],
EmailStatement::LAST30 => [now()->startOfDay()->subDays(60)->format('Y-m-d'), now()->subDays(30)->startOfDay()->format('Y-m-d')],
EmailStatement::LAST365 => [now()->startOfDay()->subDays(739)->format('Y-m-d'), now()->subDays(365)->startOfDay()->format('Y-m-d')],
EmailStatement::THIS_MONTH => [now()->startOfDay()->subMonthNoOverflow()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->subMonthNoOverflow()->lastOfMonth()->format('Y-m-d')],
EmailStatement::LAST_MONTH => [now()->startOfDay()->subMonthsNoOverflow(2)->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->subMonthNoOverflow()->lastOfMonth()->format('Y-m-d')],
EmailStatement::THIS_QUARTER => [now()->startOfDay()->subQuarterNoOverflow()->startOfQuarter()->format('Y-m-d'), now()->startOfDay()->subQuarterNoOverflow()->endOfQuarter()->format('Y-m-d')],
EmailStatement::LAST_QUARTER => [now()->startOfDay()->subQuartersNoOverflow(2)->startOfQuarter()->format('Y-m-d'), now()->startOfDay()->subQuartersNoOverflow(2)->endOfQuarter()->format('Y-m-d')],
EmailStatement::THIS_YEAR => [$fin_year_start->subYear()->format('Y-m-d'), $fin_year_start->copy()->subDay()->format('Y-m-d')],
EmailStatement::LAST_YEAR => [$fin_year_start->subYear(2)->format('Y-m-d'), $fin_year_start->copy()->subYear()->subDay()->format('Y-m-d')],
EmailStatement::CUSTOM_RANGE => [$data['start_date'], $data['end_date']],
default => [now()->startOfDay()->firstOfMonth()->format('Y-m-d'), now()->startOfDay()->lastOfMonth()->format('Y-m-d')],
};
}
} }

46
composer.lock generated
View File

@ -535,16 +535,16 @@
}, },
{ {
"name": "aws/aws-sdk-php", "name": "aws/aws-sdk-php",
"version": "3.315.0", "version": "3.315.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/aws/aws-sdk-php.git", "url": "https://github.com/aws/aws-sdk-php.git",
"reference": "a7f6026f00771025c32548dac321541face0dedc" "reference": "13871330833e167d098240dab74b8b069b9b07e3"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a7f6026f00771025c32548dac321541face0dedc", "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/13871330833e167d098240dab74b8b069b9b07e3",
"reference": "a7f6026f00771025c32548dac321541face0dedc", "reference": "13871330833e167d098240dab74b8b069b9b07e3",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -624,9 +624,9 @@
"support": { "support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues", "issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.315.0" "source": "https://github.com/aws/aws-sdk-php/tree/3.315.1"
}, },
"time": "2024-06-26T18:08:22+00:00" "time": "2024-06-27T18:03:53+00:00"
}, },
{ {
"name": "bacon/bacon-qr-code", "name": "bacon/bacon-qr-code",
@ -4609,16 +4609,16 @@
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
"version": "v11.12.0", "version": "v11.13.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/framework.git", "url": "https://github.com/laravel/framework.git",
"reference": "9a6d9cea83cfa6b9e8eda05c89741d0411d8ebe8" "reference": "92deaa4f037ff100e36809443811301819a8cf84"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/9a6d9cea83cfa6b9e8eda05c89741d0411d8ebe8", "url": "https://api.github.com/repos/laravel/framework/zipball/92deaa4f037ff100e36809443811301819a8cf84",
"reference": "9a6d9cea83cfa6b9e8eda05c89741d0411d8ebe8", "reference": "92deaa4f037ff100e36809443811301819a8cf84",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4810,7 +4810,7 @@
"issues": "https://github.com/laravel/framework/issues", "issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework" "source": "https://github.com/laravel/framework"
}, },
"time": "2024-06-25T19:33:56+00:00" "time": "2024-06-27T09:04:50+00:00"
}, },
{ {
"name": "laravel/pint", "name": "laravel/pint",
@ -11242,16 +11242,16 @@
}, },
{ {
"name": "sprain/swiss-qr-bill", "name": "sprain/swiss-qr-bill",
"version": "v4.12.1", "version": "v4.13",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sprain/php-swiss-qr-bill.git", "url": "https://github.com/sprain/php-swiss-qr-bill.git",
"reference": "3728cd1366ac631a0587c0997a4878c37923e55b" "reference": "5490e9139c4050d18533440cd9ff51a64955c035"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sprain/php-swiss-qr-bill/zipball/3728cd1366ac631a0587c0997a4878c37923e55b", "url": "https://api.github.com/repos/sprain/php-swiss-qr-bill/zipball/5490e9139c4050d18533440cd9ff51a64955c035",
"reference": "3728cd1366ac631a0587c0997a4878c37923e55b", "reference": "5490e9139c4050d18533440cd9ff51a64955c035",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -11299,7 +11299,7 @@
"description": "A PHP library to create Swiss QR bills", "description": "A PHP library to create Swiss QR bills",
"support": { "support": {
"issues": "https://github.com/sprain/php-swiss-qr-bill/issues", "issues": "https://github.com/sprain/php-swiss-qr-bill/issues",
"source": "https://github.com/sprain/php-swiss-qr-bill/tree/v4.12.1" "source": "https://github.com/sprain/php-swiss-qr-bill/tree/v4.13"
}, },
"funding": [ "funding": [
{ {
@ -11307,7 +11307,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-05-16T07:19:59+00:00" "time": "2024-06-27T11:17:56+00:00"
}, },
{ {
"name": "square/square", "name": "square/square",
@ -19238,16 +19238,16 @@
}, },
{ {
"name": "spatie/error-solutions", "name": "spatie/error-solutions",
"version": "1.0.2", "version": "1.0.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/spatie/error-solutions.git", "url": "https://github.com/spatie/error-solutions.git",
"reference": "9782ba6e25cb026cc653619e01ca695d428b3f03" "reference": "55ea4117e0fde89d520883734ab9b71064c48876"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/spatie/error-solutions/zipball/9782ba6e25cb026cc653619e01ca695d428b3f03", "url": "https://api.github.com/repos/spatie/error-solutions/zipball/55ea4117e0fde89d520883734ab9b71064c48876",
"reference": "9782ba6e25cb026cc653619e01ca695d428b3f03", "reference": "55ea4117e0fde89d520883734ab9b71064c48876",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -19300,7 +19300,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/spatie/error-solutions/issues", "issues": "https://github.com/spatie/error-solutions/issues",
"source": "https://github.com/spatie/error-solutions/tree/1.0.2" "source": "https://github.com/spatie/error-solutions/tree/1.0.3"
}, },
"funding": [ "funding": [
{ {
@ -19308,7 +19308,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-06-26T13:09:17+00:00" "time": "2024-06-27T12:22:48+00:00"
}, },
{ {
"name": "spatie/flare-client-php", "name": "spatie/flare-client-php",

View File

@ -392,7 +392,7 @@ $lang = array(
'payment_cvv' => 'Dette er det 3-4 cifrede nummer på bagsiden af dit kort', 'payment_cvv' => 'Dette er det 3-4 cifrede nummer på bagsiden af dit kort',
'payment_footer1' => '*Fakturaadressen skal matche den der er tilknyttet kortet.', 'payment_footer1' => '*Fakturaadressen skal matche den der er tilknyttet kortet.',
'payment_footer2' => '*Klik kun på "Betal Nu" én gang - transaktionen kan tage helt op til 1 minut inden den er færdig.', 'payment_footer2' => '*Klik kun på "Betal Nu" én gang - transaktionen kan tage helt op til 1 minut inden den er færdig.',
'id_number' => 'CVR/SE-nummer', 'id_number' => 'ID Nummer',
'white_label_link' => 'Hvidmærket', 'white_label_link' => 'Hvidmærket',
'white_label_header' => 'Hvidmærket', 'white_label_header' => 'Hvidmærket',
'bought_white_label' => 'Hvidmærket licens accepteret', 'bought_white_label' => 'Hvidmærket licens accepteret',
@ -2363,7 +2363,7 @@ $lang = array(
'currency_gold_troy_ounce' => 'Guld Troy Ounce', 'currency_gold_troy_ounce' => 'Guld Troy Ounce',
'currency_nicaraguan_córdoba' => 'Nicaraguanske Córdoba', 'currency_nicaraguan_córdoba' => 'Nicaraguanske Córdoba',
'currency_malagasy_ariary' => 'Madagaskars ariary', 'currency_malagasy_ariary' => 'Madagaskars ariary',
"currency_tongan_pa_anga" => "tonganske paanga", "currency_tongan_paanga" => "Tongan Pa'anga",
'review_app_help' => 'Vi håber, du nyder at bruge appen.<br/> Hvis du ville overveje :link ville vi sætte stor pris på det!', 'review_app_help' => 'Vi håber, du nyder at bruge appen.<br/> Hvis du ville overveje :link ville vi sætte stor pris på det!',
'writing_a_review' => 'skrive en anmeldelse', 'writing_a_review' => 'skrive en anmeldelse',
@ -2879,19 +2879,6 @@ $lang = array(
'refunded' => 'Refunderet', 'refunded' => 'Refunderet',
'marked_quote_as_sent' => 'Succesfuldt markeret tilbud som sendt', 'marked_quote_as_sent' => 'Succesfuldt markeret tilbud som sendt',
'custom_module_settings' => 'Speciel Modul Indstillinger', 'custom_module_settings' => 'Speciel Modul Indstillinger',
'ticket' => 'Sag',
'tickets' => 'Sager',
'ticket_number' => 'Sag #',
'new_ticket' => 'Ny sag',
'edit_ticket' => 'Redigér sag',
'view_ticket' => 'Vis sag',
'archive_ticket' => 'Arkivér sag',
'restore_ticket' => 'Genskab sag',
'delete_ticket' => 'Slet sag',
'archived_ticket' => 'Sag blev arkiveret',
'archived_tickets' => 'Sager blev arkiveret',
'restored_ticket' => 'Sag blev genskabt',
'deleted_ticket' => 'Sag blev slettet',
'open' => 'Åben', 'open' => 'Åben',
'new' => 'Ny', 'new' => 'Ny',
'closed' => 'Lukket', 'closed' => 'Lukket',
@ -2908,14 +2895,6 @@ $lang = array(
'assigned_to' => 'Tildelt', 'assigned_to' => 'Tildelt',
'reply' => 'Svar', 'reply' => 'Svar',
'awaiting_reply' => 'Afventer svar', 'awaiting_reply' => 'Afventer svar',
'ticket_close' => 'Luk sag',
'ticket_reopen' => 'Genåbn sag',
'ticket_open' => 'Åbn sag',
'ticket_split' => 'Opdel sag',
'ticket_merge' => 'Sammenflet sag',
'ticket_update' => 'Opdatér sag',
'ticket_settings' => 'Sagsindstillinger',
'updated_ticket' => 'Sag blev opdateret',
'mark_spam' => 'Marker som spam', 'mark_spam' => 'Marker som spam',
'local_part' => 'Lokal del', 'local_part' => 'Lokal del',
'local_part_unavailable' => 'Navn taget', 'local_part_unavailable' => 'Navn taget',
@ -2933,31 +2912,23 @@ $lang = array(
'mime_types' => 'Mime typer', 'mime_types' => 'Mime typer',
'mime_types_placeholder' => '. PDF , .docx, .jpg', 'mime_types_placeholder' => '. PDF , .docx, .jpg',
'mime_types_help' => 'Kommasepareret liste over tilladte mime-typer, lad tom for alle', 'mime_types_help' => 'Kommasepareret liste over tilladte mime-typer, lad tom for alle',
'ticket_number_start_help' => 'Ticket number must be greater than the current ticket number',
'new_ticket_template_id' => 'New ticket',
'new_ticket_autoresponder_help' => 'Selecting a template will send an auto response to a client/contact when a new ticket is created',
'update_ticket_template_id' => 'Updated ticket',
'update_ticket_autoresponder_help' => 'Selecting a template will send an auto response to a client/contact when a ticket is updated',
'close_ticket_template_id' => 'Closed ticket',
'close_ticket_autoresponder_help' => 'Selecting a template will send an auto response to a client/contact when a ticket is closed',
'default_priority' => 'Standardprioritet', 'default_priority' => 'Standardprioritet',
'alert_new_comment_id' => 'Ny kommentar', 'alert_new_comment_id' => 'Ny kommentar',
'alert_comment_ticket_help' => 'Hvis du vælger en skabelon, sendes der en meddelelse (til agent), når der kommer en kommentar.',
'alert_comment_ticket_email_help' => 'Kommaseparerede e-mails til bcc ved ny kommentar.',
'new_ticket_notification_list' => 'Yderligere nye Sag -meddelelser',
'update_ticket_notification_list' => 'Yderligere meddelelser om nye kommentarer', 'update_ticket_notification_list' => 'Yderligere meddelelser om nye kommentarer',
'comma_separated_values' => 'admin@example.com, supervisor@example.com', 'comma_separated_values' => 'admin@example.com, supervisor@example.com',
'alert_ticket_assign_agent_id' => 'Tildeling af sag',
'alert_ticket_assign_agent_id_hel' => 'Valg af en skabelon vil sende en meddelelse (til agent), når en Sag er tildelt.',
'alert_ticket_assign_agent_id_notifications' => 'Yderligere Sag tildelte meddelelser',
'alert_ticket_assign_agent_id_help' => 'Kommaseparerede e-mails til bcc på Sag opgave.',
'alert_ticket_transfer_email_help' => 'Kommaseparerede e-mails til bcc ved Sag overførsel.',
'alert_ticket_overdue_agent_id' => 'Sag er forfalden',
'alert_ticket_overdue_email' => 'Yderligere forfaldne Sag -meddelelser',
'alert_ticket_overdue_email_help' => 'Kommaseparerede e-mails til bcc på Sag forsinket.',
'alert_ticket_overdue_agent_id_help' => 'Valg af en skabelon vil sende en meddelelse (til agent), når en Sag bliver forsinket.',
'default_agent' => 'Standardagent', 'default_agent' => 'Standardagent',
'default_agent_help' => 'Hvis valgt vil det automatisk blive tildelt alle indgående billetter', 'default_agent_help' => 'Hvis valgt vil det automatisk blive tildelt alle indgående billetter',
'show_agent_details' => 'Vis agentoplysninger om svar', 'show_agent_details' => 'Vis agentoplysninger om svar',
'avatar' => 'Avatar', 'avatar' => 'Avatar',
'remove_avatar' => 'Fjern avatar', 'remove_avatar' => 'Fjern avatar',
'ticket_not_found' => 'Sag blev ikke fundet',
'add_template' => 'Tilføj skabelon', 'add_template' => 'Tilføj skabelon',
'updated_ticket_template' => 'Sagsskabelon blev opdateret',
'created_ticket_template' => 'Sagsskabelon blev oprettet',
'archive_ticket_template' => 'Arkiv skabelon', 'archive_ticket_template' => 'Arkiv skabelon',
'restore_ticket_template' => 'Genskab skabelon', 'restore_ticket_template' => 'Genskab skabelon',
'archived_ticket_template' => 'Succesfuldt arkiveret skabelon', 'archived_ticket_template' => 'Succesfuldt arkiveret skabelon',
@ -3130,7 +3101,7 @@ $lang = array(
'sign_up_with_google' => 'Tilmeld dig med Google', 'sign_up_with_google' => 'Tilmeld dig med Google',
'long_press_multiselect' => 'Tryk længe på Multiselect', 'long_press_multiselect' => 'Tryk længe på Multiselect',
'migrate_to_next_version' => 'Migrer til den næste version af Faktura Ninja', 'migrate_to_next_version' => 'Migrer til den næste version af Faktura Ninja',
'migrate_intro_text' => 'Vi har arbejdet på næste version af Faktura Ninja. Klik på knappen nedenfor for at starte migreringen.', 'migrate_intro_text' => 'Vi har arbejdet på næste version af Invoice Ninja. Klik på knappen nedenfor for at starte migreringen.',
'start_the_migration' => 'Start migreringen', 'start_the_migration' => 'Start migreringen',
'migration' => 'Migration', 'migration' => 'Migration',
'welcome_to_the_new_version' => 'Velkommen til den nye version af Faktura Ninja', 'welcome_to_the_new_version' => 'Velkommen til den nye version af Faktura Ninja',
@ -4504,7 +4475,7 @@ $lang = array(
'view_purchase_order' => 'Vis Indkøbsordre', 'view_purchase_order' => 'Vis Indkøbsordre',
'purchase_orders_backup_subject' => 'Dine indkøbsordrer er klar til download', 'purchase_orders_backup_subject' => 'Dine indkøbsordrer er klar til download',
'notification_purchase_order_viewed_subject' => 'Indkøbsordre :invoice blev set af :client', 'notification_purchase_order_viewed_subject' => 'Indkøbsordre :invoice blev set af :client',
'notification_purchase_order_viewed' => 'Følgende Sælger :client har set Indkøbsordre :invoice for :amount .', 'notification_purchase_order_viewed' => 'Følgende leverandør :client har set Indkøbsordre :invoice for :amount .',
'purchase_order_date' => 'Dato for købsordre', 'purchase_order_date' => 'Dato for købsordre',
'purchase_orders' => 'Indkøbsordre', 'purchase_orders' => 'Indkøbsordre',
'purchase_order_number_placeholder' => 'Indkøbsordre nr. :purchase_order', 'purchase_order_number_placeholder' => 'Indkøbsordre nr. :purchase_order',
@ -5302,6 +5273,19 @@ $lang = array(
'currency_bhutan_ngultrum' => 'Bhutan Ngultrum', 'currency_bhutan_ngultrum' => 'Bhutan Ngultrum',
'end_of_month' => 'End Of Month', 'end_of_month' => 'End Of Month',
'merge_e_invoice_to_pdf' => 'Merge E-Invoice and PDF', 'merge_e_invoice_to_pdf' => 'Merge E-Invoice and PDF',
'task_assigned_subject' => 'New task assignment [Task :task] [ :date ]',
'task_assigned_body' => 'You have been assigned task :task <br><br> Description: :description <br><br> Client: :client',
'activity_141' => 'User :user entered note: :notes',
'quote_reminder_subject' => 'Reminder: Quote :quote from :company',
'quote_reminder_message' => 'Reminder for quote :number for :amount',
'quote_reminder1' => 'First Quote Reminder',
'before_valid_until_date' => 'Before the valid until date',
'after_valid_until_date' => 'After the valid until date',
'after_quote_date' => 'After the quote date',
'remind_quote' => 'Remind Quote',
'end_of_month' => 'End Of Month',
'tax_currency_mismatch' => 'Tax currency is different from invoice currency',
'edocument_import_already_exists' => '\nThe invoice has already been imported on :date'
); );
return $lang; return $lang;

View File

@ -5288,7 +5288,16 @@ $lang = array(
'remind_quote' => 'Remind Quote', 'remind_quote' => 'Remind Quote',
'end_of_month' => 'End Of Month', 'end_of_month' => 'End Of Month',
'tax_currency_mismatch' => 'Tax currency is different from invoice currency', 'tax_currency_mismatch' => 'Tax currency is different from invoice currency',
'edocument_import_already_exists' => '\nThe invoice has already been imported on :date' 'edocument_import_already_exists' => '\nThe invoice has already been imported on :date',
'before_valid_until' => 'Before the valid until',
'after_valid_until' => 'After the valid until',
'task_assigned_notification' => 'Task Assigned Notification',
'task_assigned_notification_help' => 'Send an email when a task is assigned',
'invoices_locked_end_of_month' => 'Invoices are locked at the end of the month',
'referral_url' => 'Referral URL',
'add_comment' => 'Add Comment',
'added_comment' => 'Successfully saved comment',
'tickets' => 'Tickets',
); );
return $lang; return $lang;

View File

@ -2361,7 +2361,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'currency_gold_troy_ounce' => 'Once troy d\'or', 'currency_gold_troy_ounce' => 'Once troy d\'or',
'currency_nicaraguan_córdoba' => 'Cordoba nicaraguayen', 'currency_nicaraguan_córdoba' => 'Cordoba nicaraguayen',
'currency_malagasy_ariary' => 'Ariary malgache', 'currency_malagasy_ariary' => 'Ariary malgache',
"currency_tongan_pa_anga" => "Pa'anga tongien", "currency_tongan_paanga" => "Pa'anga tongien",
'review_app_help' => 'Nous espérons que votre utilisation de cette application vous est agréable.<br/>Un commentaire de votre part serait grandement apprécié!', 'review_app_help' => 'Nous espérons que votre utilisation de cette application vous est agréable.<br/>Un commentaire de votre part serait grandement apprécié!',
'writing_a_review' => 'rédiger un commentaire', 'writing_a_review' => 'rédiger un commentaire',
@ -2877,19 +2877,6 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'refunded' => 'Remboursé', 'refunded' => 'Remboursé',
'marked_quote_as_sent' => 'La soumission a été marquée comme envoyée', 'marked_quote_as_sent' => 'La soumission a été marquée comme envoyée',
'custom_module_settings' => 'Paramètres personnalisés de modules', 'custom_module_settings' => 'Paramètres personnalisés de modules',
'ticket' => 'Billet',
'tickets' => 'Billets',
'ticket_number' => 'Billet #',
'new_ticket' => 'Nouveau billet',
'edit_ticket' => 'Éditer le billet',
'view_ticket' => 'Voir le billet',
'archive_ticket' => 'Archiver le billet',
'restore_ticket' => 'Restaurer le billet',
'delete_ticket' => 'Supprimer le billet',
'archived_ticket' => 'Le billet a été archivé',
'archived_tickets' => 'Les billets ont été archivés',
'restored_ticket' => 'Le billet a été restauré',
'deleted_ticket' => 'Le billet a été supprimé',
'open' => 'Ouvert', 'open' => 'Ouvert',
'new' => 'Nouveau', 'new' => 'Nouveau',
'closed' => 'Désactivé', 'closed' => 'Désactivé',
@ -2906,14 +2893,6 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'assigned_to' => 'Assigné à', 'assigned_to' => 'Assigné à',
'reply' => 'Répondre', 'reply' => 'Répondre',
'awaiting_reply' => 'En attente de réponse', 'awaiting_reply' => 'En attente de réponse',
'ticket_close' => 'Fermer le billet',
'ticket_reopen' => 'Réouvrir le billet',
'ticket_open' => 'Ouvrir le billet',
'ticket_split' => 'Scinder le billet',
'ticket_merge' => 'Fusionner le billet',
'ticket_update' => 'Mettre à jour le billet',
'ticket_settings' => 'Paramètres des billets',
'updated_ticket' => 'Billet mis à jour',
'mark_spam' => 'Marquer comme pourriel', 'mark_spam' => 'Marquer comme pourriel',
'local_part' => 'Partie locale', 'local_part' => 'Partie locale',
'local_part_unavailable' => 'Nom déjà pris', 'local_part_unavailable' => 'Nom déjà pris',
@ -2931,31 +2910,23 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'mime_types' => 'Type MIME', 'mime_types' => 'Type MIME',
'mime_types_placeholder' => '.pdf , .docx, .jpg', 'mime_types_placeholder' => '.pdf , .docx, .jpg',
'mime_types_help' => 'Liste séparée par une virgule pour les types MIME autorisés. Laissant vide pour tout autoriser', 'mime_types_help' => 'Liste séparée par une virgule pour les types MIME autorisés. Laissant vide pour tout autoriser',
'ticket_number_start_help' => 'Le numéro du billet doit être supérieur au numéro de billet actuel.',
'new_ticket_template_id' => 'Nouveau billet',
'new_ticket_autoresponder_help' => 'La sélection d\'un modèle enverra automatiquement une réponse automatique à un client/contact lors de la création d\'un nouveau billet.',
'update_ticket_template_id' => 'Le billet a été mis à jour',
'update_ticket_autoresponder_help' => 'La sélection d\'un modèle déclenchera automatiquement l\'envoi d\'une réponse automatique à un client/contact lors de la mise à jour d\'un billet.',
'close_ticket_template_id' => 'Le billet a ét fermé',
'close_ticket_autoresponder_help' => 'La sélection d\'un modèle enverra automatiquement une réponse automatique à un client/contact lors de la fermeture d\'un billet.',
'default_priority' => 'Priorité par défaut', 'default_priority' => 'Priorité par défaut',
'alert_new_comment_id' => 'Nouveau commentaire', 'alert_new_comment_id' => 'Nouveau commentaire',
'alert_comment_ticket_help' => 'En sélectionnant un modèle, une notification (à l\'agent) sera envoyée lorsqu\'un commentaire est fait',
'alert_comment_ticket_email_help' => 'Courriels séparés par une virgule pour Cci sur un nouveau commentaire.',
'new_ticket_notification_list' => 'Notifications de nouveaux billets additionnels',
'update_ticket_notification_list' => 'Notifications de nouveaux commentaires additionnels', 'update_ticket_notification_list' => 'Notifications de nouveaux commentaires additionnels',
'comma_separated_values' => 'admin@exemple.com, supervisor@exemple.com', 'comma_separated_values' => 'admin@exemple.com, supervisor@exemple.com',
'alert_ticket_assign_agent_id' => 'Assignation de billet',
'alert_ticket_assign_agent_id_hel' => 'En sélectionnant un modèle, une notification (à l\'agent) sera envoyée lorsqu\'un billet est assigné.',
'alert_ticket_assign_agent_id_notifications' => 'Notifications de billets assignés additionnels',
'alert_ticket_assign_agent_id_help' => 'Courriels séparés par une virgule pour Cci pour un billet assigné.',
'alert_ticket_transfer_email_help' => 'Courriels séparés par une virgule pour Cci sur un billet transféré.',
'alert_ticket_overdue_agent_id' => 'Billet en retard',
'alert_ticket_overdue_email' => 'Notifications de billets en retard additionnels',
'alert_ticket_overdue_email_help' => 'Courriels séparés par une virgule pour Cci sur un billet en retard.',
'alert_ticket_overdue_agent_id_help' => 'En sélectionnant un modèle, une notification (à l\'agent) sera envoyée lorsqu\'un billet est en retard.',
'default_agent' => 'Agent par défaut', 'default_agent' => 'Agent par défaut',
'default_agent_help' => 'Cette sélection va automatiquement être assignée à tous les courriels entrants', 'default_agent_help' => 'Cette sélection va automatiquement être assignée à tous les courriels entrants',
'show_agent_details' => 'Afficher les informations de l\'agent dans les réponses', 'show_agent_details' => 'Afficher les informations de l\'agent dans les réponses',
'avatar' => 'Avatar', 'avatar' => 'Avatar',
'remove_avatar' => 'Retirer l\'avatar', 'remove_avatar' => 'Retirer l\'avatar',
'ticket_not_found' => 'Billet introuvable',
'add_template' => 'Ajouter un modèle', 'add_template' => 'Ajouter un modèle',
'updated_ticket_template' => 'Modèle de billets mise à jour',
'created_ticket_template' => 'Modèle de billet créés',
'archive_ticket_template' => 'Archiver le modèle', 'archive_ticket_template' => 'Archiver le modèle',
'restore_ticket_template' => 'Restaurer le modèle', 'restore_ticket_template' => 'Restaurer le modèle',
'archived_ticket_template' => 'Le modèle a été archivé', 'archived_ticket_template' => 'Le modèle a été archivé',
@ -5300,6 +5271,19 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
'currency_bhutan_ngultrum' => 'Ngultrum Bhoutan', 'currency_bhutan_ngultrum' => 'Ngultrum Bhoutan',
'end_of_month' => 'Fin de mois', 'end_of_month' => 'Fin de mois',
'merge_e_invoice_to_pdf' => 'Fusionner E-Facture et PDF', 'merge_e_invoice_to_pdf' => 'Fusionner E-Facture et PDF',
'task_assigned_subject' => 'Nouvelle tâche assignée [Tâche:task] [ :date ]',
'task_assigned_body' => 'Vous avez été assigné à la tâche :task <br><br> Description: :description <br><br> Client: :client',
'activity_141' => 'L\'utilisateur:user a saisi la note: :notes',
'quote_reminder_subject' => 'Rappel: Soumission:quote de :company',
'quote_reminder_message' => 'Rappel pour la soumission :number de :amount',
'quote_reminder1' => 'Rappel pour la première soumission',
'before_valid_until_date' => 'Avant la date Valide jusqu\'au',
'after_valid_until_date' => 'Après la date Valide jusqu\'au',
'after_quote_date' => 'Après al date de soumission',
'remind_quote' => 'Rappel de soumission',
'end_of_month' => 'Fin de mois',
'tax_currency_mismatch' => 'La devise de la taxe est différente de la devise de la facture.',
'edocument_import_already_exists' => '\nLa facture a déjà été importée le :date'
); );
return $lang; return $lang;

View File

@ -31,4 +31,10 @@ parameters:
- '#Access to protected property#' - '#Access to protected property#'
- '#Call to undefined method .*#' - '#Call to undefined method .*#'
- '#Argument of an invalid type stdClass supplied for foreach, only iterables are supported.#' - '#Argument of an invalid type stdClass supplied for foreach, only iterables are supported.#'
- '#Comparison operation ">=" between int<1, max> and 1 is always true#' - '#Comparison operation ">=" between int<1, max> and 1 is always true#'
- '#Negated boolean expression is always#'
- '#Ternary operator condition#'
- '#Expression on left side of ?? is not nullable.#'
- '#Left side of && is always true.#'
- '#Right side of && is always true.#'

View File

@ -16,9 +16,9 @@ const RESOURCES = {"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
"canvaskit/canvaskit.js": "c86fbd9e7b17accae76e5ad116583dc4", "canvaskit/canvaskit.js": "c86fbd9e7b17accae76e5ad116583dc4",
"canvaskit/canvaskit.wasm": "3d2a2d663e8c5111ac61a46367f751ac", "canvaskit/canvaskit.wasm": "3d2a2d663e8c5111ac61a46367f751ac",
"canvaskit/skwasm.wasm": "e42815763c5d05bba43f9d0337fa7d84", "canvaskit/skwasm.wasm": "e42815763c5d05bba43f9d0337fa7d84",
"version.json": "1592dbbd49cf08963e29ab3a85640d96", "version.json": "f789e711f61e122f41a7eda7522a1fba",
"favicon.png": "dca91c54388f52eded692718d5a98b8b", "favicon.png": "dca91c54388f52eded692718d5a98b8b",
"main.dart.js": "0512d8a34cb5c2d5d565a27c3666d9f9", "main.dart.js": "bb6cfbe200a5c6a0d8857eaffd21b759",
"assets/NOTICES": "412b336cf9e33e70058d612857effae1", "assets/NOTICES": "412b336cf9e33e70058d612857effae1",
"assets/AssetManifest.bin": "bf3be26e7055ad9a32f66b3a56138224", "assets/AssetManifest.bin": "bf3be26e7055ad9a32f66b3a56138224",
"assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219", "assets/assets/images/logo_light.png": "e5f46d5a78e226e7a9553d4ca6f69219",
@ -307,7 +307,7 @@ const RESOURCES = {"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
"assets/FontManifest.json": "087fb858dc3cbfbf6baf6a30004922f1", "assets/FontManifest.json": "087fb858dc3cbfbf6baf6a30004922f1",
"assets/fonts/MaterialIcons-Regular.otf": "a57618538ab8b4c4081d4491870ac333", "assets/fonts/MaterialIcons-Regular.otf": "a57618538ab8b4c4081d4491870ac333",
"assets/AssetManifest.json": "759f9ef9973f7e26c2a51450b55bb9fa", "assets/AssetManifest.json": "759f9ef9973f7e26c2a51450b55bb9fa",
"/": "a53ace1edfc2ce12fc50cbbade610be3", "/": "e847f898173e2b358dcf6efc58032f91",
"favicon.ico": "51636d3a390451561744c42188ccd628", "favicon.ico": "51636d3a390451561744c42188ccd628",
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40"}; "manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40"};
// The application shell files that are downloaded before a service worker can // The application shell files that are downloaded before a service worker can

271459
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

269785
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

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"app_name":"invoiceninja_flutter","version":"5.0.160","build_number":"160","package_name":"invoiceninja_flutter"} {"app_name":"invoiceninja_flutter","version":"5.0.161","build_number":"161","package_name":"invoiceninja_flutter"}

View File

@ -81,7 +81,7 @@
name="country_id"> name="country_id">
<option value="none"></option> <option value="none"></option>
@foreach(App\Utils\TranslationHelper::getCountries() as $country) @foreach(App\Utils\TranslationHelper::getCountries() as $country)
<option <option value="{{ $country->id }}">
{{ $country->iso_3166_2 }} {{ $country->iso_3166_2 }}
({{ $country->name }}) ({{ $country->name }})
</option> </option>

View File

@ -1,4 +1,4 @@
@extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.payment_type_credit_card'), 'card_title' => '']) @extends('portal.ninja2020.layout.payments', ['gateway_title' => ctrans('texts.paypal'), 'card_title' => ''])
@section('gateway_head') @section('gateway_head')

View File

@ -11,7 +11,9 @@
namespace Tests\Feature; namespace Tests\Feature;
use App\Models\Client;
use App\Models\CompanyGateway; use App\Models\CompanyGateway;
use App\Models\Invoice;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\MockAccountData; use Tests\MockAccountData;
use Tests\TestCase; use Tests\TestCase;
@ -44,6 +46,122 @@ class ClientModelTest extends TestCase
} }
public function testNewWithoutAndDeletedClientFilters()
{
$this->invoice->amount = 10;
$this->invoice->balance = 10;
$this->invoice->status_id=2;
$this->invoice->date = now()->subDays(2);
$this->invoice->due_date = now()->addDays(2);
$this->invoice->save();
$cd = Client::factory()->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id,
]);
$cd2 = Client::factory()->create([
'company_id' => $this->company->id,
'user_id' => $this->user->id,
]);
$invoice_count = Invoice::where('company_id', $this->company->id)->count();
$this->assertGreaterThan(0, $invoice_count);
$i = Invoice::factory()->create([
'client_id' => $cd->id,
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'status_id' => 2,
'amount' => 10,
'balance' => 10,
'date' => now()->subDays(2)->format('Y-m-d'),
'due_date' => now()->addDays(5)->format('Y-m-d'),
]);
$i2 = Invoice::factory()->create([
'client_id' => $cd2->id,
'user_id' => $this->user->id,
'company_id' => $this->company->id,
'status_id' => 2,
'amount' => 10,
'balance' => 10,
'date' => now()->subDays(2)->format('Y-m-d'),
'due_date' => now()->addDays(5)->format('Y-m-d'),
]);
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->get('/api/v1/invoices?status=active');
$response->assertStatus(200);
$arr = $response->json();
$this->assertEquals($invoice_count+2, count($arr['data']));
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->get('/api/v1/invoices?upcoming=true&status=active&include=client');
$response->assertStatus(200);
$arr = $response->json();
$this->assertEquals($invoice_count + 2, count($arr['data']));
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->get('/api/v1/invoices?upcoming=true&status=active&without_deleted_clients=true');
$response->assertStatus(200);
$arr = $response->json();
$this->assertEquals($invoice_count + 2, count($arr['data']));
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->get('/api/v1/invoices?upcoming=true&status=active&filter_deleted_clients=true');
$response->assertStatus(200);
$arr = $response->json();
$this->assertEquals($invoice_count + 2, count($arr['data']));
$cd2->is_deleted = true;
$cd2->save();
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->get('/api/v1/invoices?upcoming=true&status=active&without_deleted_clients=true');
$response->assertStatus(200);
$arr = $response->json();
$this->assertEquals($invoice_count + 1, count($arr['data']));
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->get('/api/v1/invoices?upcoming=true&status=active&filter_deleted_clients=true');
$response->assertStatus(200);
$arr = $response->json();
$this->assertEquals($invoice_count + 1, count($arr['data']));
}
public function testPaymentMethodsWithCreditsEnforced() public function testPaymentMethodsWithCreditsEnforced()
{ {
@ -51,6 +169,6 @@ class ClientModelTest extends TestCase
$this->assertGreaterThan(0, CompanyGateway::count()); $this->assertGreaterThan(0, CompanyGateway::count());
$this->assertEquals(1, count($payment_methods)); $this->assertEquals(2, count($payment_methods));
} }
} }

View File

@ -189,6 +189,22 @@ class TaskApiTest extends TestCase
} }
public function testTaskDivisionByZero()
{
$data = [
"rate" => 0,
"time_log" => '[[1719350900,1719352700,"",true]]',
];
$response = $this->withHeaders([
'X-API-SECRET' => config('ninja.api_secret'),
'X-API-TOKEN' => $this->token,
])->postJson("/api/v1/tasks", $data);
$response->assertStatus(200);
}
public function testRequestRuleParsing() public function testRequestRuleParsing()
{ {