1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 05:02:36 +01:00

Updates for country selector

This commit is contained in:
David Bomba 2024-06-29 07:42:45 +10:00
parent 028eb24fd9
commit dd1fc3da82
7 changed files with 344 additions and 4 deletions

View File

@ -11,8 +11,9 @@
namespace App\Http\Controllers;
use App\Http\Requests\Chart\ShowChartRequest;
use App\Services\Chart\ChartService;
use App\Http\Requests\Chart\ShowChartRequest;
use App\Http\Requests\Chart\ShowCalculatedFieldRequest;
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);
}
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

@ -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

@ -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\Company;
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 Illuminate\Support\Facades\Cache;
class ChartService
{
use ChartQueries;
use ChartCalculations;
public function __construct(public Company $company, private User $user, private bool $is_admin)
{
@ -71,7 +76,7 @@ class ChartService
return $final_currencies;
}
/* Chart Data */
public function chart_summary($start_date, $end_date): array
{
@ -207,4 +212,44 @@ class ChartService
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

@ -85,6 +85,7 @@ class SystemHealth
'file_permissions' => (string) self::checkFileSystem(),
'exchange_rate_api_not_configured' => (bool)self::checkCurrencySanity(),
'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']) {
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')],
@ -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')],
};
}
}

View File

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