Implement adequate report filtering for lesser permissioned users

This commit is contained in:
David Bomba 2025-10-29 09:27:48 +11:00
parent b21c182eda
commit 16a380972f
35 changed files with 163 additions and 45 deletions

View File

@ -115,6 +115,8 @@ class ActivityExport extends BaseExport
$query = $this->addDateRange($query, 'activities');
$query = $this->filterByUserPermissions($query);
if ($this->input['activity_type_id'] ?? false) {
$query->where('activity_type_id', $this->input['activity_type_id']);
}
@ -133,12 +135,10 @@ class ActivityExport extends BaseExport
//insert the header
$this->csv->insertOne($this->buildHeader());
$query->cursor()
->each(function ($entity) {
/** @var \App\Models\Activity $entity */
$this->buildRow($entity);
});

View File

@ -1710,4 +1710,77 @@ $products = str_getcsv($this->input['product_key'], ',', "'");
return $entity;
}
public function filterByUserPermissions(Builder $query): Builder
{
$user = User::withTrashed()->where('id', $this->input['user_id'])->where('account_id', $this->company->account_id)->first();
if ($user->isAdmin() || $user->hasExactPermission('view_all') || $user->hasExactPermission('edit_all')) { // No State? Do we need to ensure -> isAdmin() binds to the correct company?
return $query;
}
if($user->hasExactPermission('create_all')){
return $query->where('user_id', $user->id);
}
return $this->resolveEntityFilters($user, $query);
}
private function resolveEntityFilters(User $user, Builder $query): Builder
{
$model = get_class($query->getModel());
$column_listing = \Illuminate\Support\Facades\Schema::getColumnListing($query->getModel()->getTable());
$model_string = match($model) {
'App\Models\Client' => 'client',
'App\Models\ClientContact' => 'client',
'App\Models\Invoice' => 'invoice',
'App\Models\Quote' => 'quote',
'App\Models\Credit' => 'credit',
'App\Models\PurchaseOrder' => 'purchase_order',
'App\Models\RecurringInvoice' => 'recurring_invoice',
'App\Models\RecurringExpense' => 'recurring_expense',
'App\Models\Task' => 'task',
'App\Models\Vendor' => 'vendor',
'App\Models\VendorContact' => 'vendor_contact',
'App\Models\Product' => 'product',
'App\Models\Payment' => 'payment',
'App\Models\Expense' => 'expense',
'App\Models\Document' => 'document',
'App\Models\Activity' => 'activity',
'App\Models\Task' => 'task',
'App\Models\Project' => 'project',
default => false,
};
/** If the User can view or edit the entity, then return the query unfiltered */
if($user->hasIntersectPermissions(["view_{$model_string}", "edit_{$model_string}"])){
return $query;
}
//Handle Child Models Like ClientContact or VendorContact
if(in_array($model, ['App\Models\ClientContact', 'App\Models\VendorContact'])){
$query->whereHas($model_string, function ($_q) use ($user){
$_q->where('user_id', $user->id)->orWhere('assigned_user_id', $user->id);
});
return $query;
}
return $query->where(function ($q) use ($user, $column_listing){
if(in_array('user_id', $column_listing)){
$q->where('user_id', $user->id);
}
if(in_array('assigned_user_id', $column_listing)){
$q->orWhere('assigned_user_id', $user->id);
}
});
}
}

View File

@ -136,6 +136,8 @@ class ClientExport extends BaseExport
$query = $this->addDateRange($query, ' clients');
$query = $this->filterByUserPermissions($query);
if ($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query);
}

View File

@ -65,6 +65,7 @@ class ContactExport extends BaseExport
});
$query = $this->addDateRange($query, 'client_contacts');
$query = $this->filterByUserPermissions($query);
return $query;

View File

@ -123,6 +123,8 @@ class CreditExport extends BaseExport
$query = $this->addCreditStatusFilter($query, $this->input['status']);
}
$query = $this->filterByUserPermissions($query);
if ($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query);
}

View File

@ -81,6 +81,8 @@ class DocumentExport extends BaseExport
$query = $this->addDateRange($query, 'documents');
$query = $this->filterByUserPermissions($query);
if ($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query);
}

View File

@ -114,6 +114,8 @@ class ExpenseExport extends BaseExport
$query = $this->addCategoryFilter($query, $this->input['categories']);
}
$query = $this->filterByUserPermissions($query);
if ($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query);
}

View File

@ -82,6 +82,9 @@ class InvoiceExport extends BaseExport
$query = $this->addInvoiceStatusFilter($query, $this->input['status']);
}
$query = $this->filterByUserPermissions($query);
if ($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query);
}

View File

@ -93,6 +93,8 @@ class InvoiceItemExport extends BaseExport
$query = $this->addInvoiceStatusFilter($query, $this->input['status']);
}
$query = $this->filterByUserPermissions($query);
$query = $this->applyProductFilters($query);
if ($this->input['document_email_attachment'] ?? false) {

View File

@ -72,6 +72,7 @@ class PaymentExport extends BaseExport
}
$query = $this->addPaymentStatusFilters($query, $this->input['status'] ?? '');
$query = $this->filterByUserPermissions($query);
if ($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query);

View File

@ -83,6 +83,7 @@ class ProductExport extends BaseExport
}
$query = $this->addDateRange($query, 'products');
$query = $this->filterByUserPermissions($query);
if ($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query);

View File

@ -128,6 +128,8 @@ class ProductSalesExport extends BaseExport
$query = $this->filterByClients($query);
$query = $this->filterByUserPermissions($query);
$query = $this->filterByProducts($query);
$this->csv->insertOne($this->buildHeader());

View File

@ -76,6 +76,7 @@ class PurchaseOrderExport extends BaseExport
if ($clients) {
$query = $this->addClientFilter($query, $clients);
}
$query = $this->filterByUserPermissions($query);
$query = $this->addPurchaseOrderStatusFilter($query, $this->input['status'] ?? '');

View File

@ -79,6 +79,7 @@ class PurchaseOrderItemExport extends BaseExport
if ($clients) {
$query = $this->addClientFilter($query, $clients);
}
$query = $this->filterByUserPermissions($query);
$query = $this->addPurchaseOrderStatusFilter($query, $this->input['status'] ?? '');

View File

@ -78,6 +78,8 @@ class QuoteExport extends BaseExport
$query = $this->addQuoteStatusFilter($query, $this->input['status'] ?? '');
$query = $this->filterByUserPermissions($query);
if ($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query);
}

View File

@ -82,6 +82,7 @@ class QuoteItemExport extends BaseExport
if ($clients) {
$query = $this->addClientFilter($query, $clients);
}
$query = $this->filterByUserPermissions($query);
$query = $this->addQuoteStatusFilter($query, $this->input['status'] ?? '');

View File

@ -73,6 +73,7 @@ class RecurringInvoiceExport extends BaseExport
if ($clients) {
$query = $this->addClientFilter($query, $clients);
}
$query = $this->filterByUserPermissions($query);
$query = $this->addRecurringInvoiceStatusFilter($query, $this->input['status'] ?? '');

View File

@ -93,6 +93,7 @@ class RecurringInvoiceItemExport extends BaseExport
if ($this->input['status'] ?? false) {
$query = $this->addRecurringInvoiceStatusFilter($query, $this->input['status']);
}
$query = $this->filterByUserPermissions($query);
$query = $this->applyProductFilters($query);

View File

@ -82,6 +82,12 @@ class TaskExport extends BaseExport
$query = $this->addClientFilter($query, $clients);
}
if($this->input['status'] ?? false){
$query = $this->addTaskStatusFilter($query, $this->input['status']);
}
$query = $this->filterByUserPermissions($query);
$document_attachments = &$this->input['document_email_attachment'];
if ($document_attachments) {
@ -261,6 +267,7 @@ class TaskExport extends BaseExport
*/
protected function addTaskStatusFilter(Builder $query, string $status): Builder
{
nlog(['addTaskStatusFilter', $status]);
/** @var array $status_parameters */
$status_parameters = explode(',', $status);
@ -276,6 +283,16 @@ class TaskExport extends BaseExport
$query->whereNull('invoice_id');
}
$keys = $this->transformKeys($status_parameters);
$keys = collect($keys)->filter(function ($key){
return is_int($key);
})->toArray();
if(count($keys) > 0){
$query->whereIn('status_id', $keys);
}
return $query;
}

View File

@ -70,6 +70,7 @@ class VendorExport extends BaseExport
}
$query = $this->addDateRange($query, 'vendors');
$query = $this->filterByUserPermissions($query);
if ($this->input['document_email_attachment'] ?? false) {
$this->queueDocuments($query);

View File

@ -1089,7 +1089,7 @@ class BaseController extends Controller
$data = $this->first_load;
}
} else {
$included = request()->input('include', '');
$included = request()->input('include') ?? '';
$included = explode(',', $included);
foreach ($included as $include) {

View File

@ -31,8 +31,6 @@ class ReportPreviewController extends BaseController
$report = Cache::get($hash);
nlog($report);
if (!$report) {
return response()->json(['message' => 'Still working.....'], 409);
}

View File

@ -70,10 +70,12 @@ class ProductSalesReportRequest extends Request
$input['end_date'] = null;
}
if (array_key_exists('client_id', $input) && strlen($input['client_id']) >= 1) {
if (array_key_exists('client_id', $input) && strlen($input['client_id'] ?? '') > 1) {
$input['client_id'] = $this->decodePrimaryKey($input['client_id']);
}
$input['user_id'] = auth()->user()->id;
$this->replace($input);
}
@ -89,7 +91,7 @@ class ProductSalesReportRequest extends Request
return false;
}
return $user->isAdmin() || $user->hasPermission('view_reports');
return $user->isAdmin() || ($user->hasPermission('view_all') && $user->hasPermission('view_reports'));
}

View File

@ -51,6 +51,9 @@ class ProfitLossRequest extends Request
$input['date_range'] = 'all';
}
$input['user_id'] = auth()->user()->id;
$this->replace($input);
}
@ -66,7 +69,7 @@ class ProfitLossRequest extends Request
return false;
}
return $user->isAdmin() || $user->hasPermission('view_reports');
return $user->isAdmin() || ($user->hasPermission('view_all') && $user->hasPermission('view_reports'));
}

View File

@ -69,6 +69,8 @@ class ProjectReportRequest extends Request
$input['end_date'] = null;
}
$input['user_id'] = auth()->user()->id;
$this->replace($input);
}

View File

@ -56,7 +56,7 @@ class PreviewReport implements ShouldQueue
/**
* Handle a job failure.
*/
public function failed(\Throwable $exception = null)
public function failed(?\Throwable $exception)
{
if($exception) {
nlog("EXCEPTION:: PreviewReport:: could not preview report for " . $exception->getMessage());

View File

@ -37,25 +37,13 @@ class SendToAdmin implements ShouldQueue
use Queueable;
use SerializesModels;
protected Company $company;
protected array $request;
protected string $report_class;
protected string $file_name;
public $tries = 1;
/**
* Create a new job instance.
*/
public function __construct(Company $company, array $request, $report_class, $file_name)
public function __construct(protected Company $company, protected array $request, protected string $report_class, protected string $file_name)
{
$this->company = $company;
$this->request = $request;
$this->report_class = $report_class;
$this->file_name = $file_name;
}
public function handle()
@ -69,7 +57,6 @@ class SendToAdmin implements ShouldQueue
$file_name = $this->file_name;
$size_mb = round(strlen($csv) / (1024 * 1024), 2); // Size in MB
nlog("Report Size: MB " . $size_mb);
// If the file is greater than 5MB, we need to zip it to ensure it does not break attachment size limits
if($size_mb > 5){
@ -116,12 +103,7 @@ class SendToAdmin implements ShouldQueue
}
// public function middleware()
// {
// return [(new WithoutOverlapping("report-{$this->company->company_key}-{$this->report_class}"))->expireAfter(60)];
// }
public function failed(\Throwable $exception = null)
public function failed(?\Throwable $exception = null)
{
if($exception) {
nlog("EXCEPTION:: SendToAdmin:: could not email report for" . $exception->getMessage());

View File

@ -471,10 +471,10 @@ class User extends Authenticatable implements MustVerifyEmail
}
return $this->isSuperUser() ||
(stripos($this->token()->cu->permissions, $permission) !== false) ||
(stripos($this->token()->cu->permissions, $all_permission) !== false) ||
(stripos($this->token()->cu->permissions, $edit_all) !== false) ||
(stripos($this->token()->cu->permissions, $edit_entity) !== false);
(stripos($this->token()->cu->permissions ?? '', $permission) !== false) ||
(stripos($this->token()->cu->permissions ?? '', $all_permission) !== false) ||
(stripos($this->token()->cu->permissions ?? '', $edit_all) !== false) ||
(stripos($this->token()->cu->permissions ?? '', $edit_entity) !== false);
}
/**

View File

@ -103,6 +103,7 @@ class ARDetailReport extends BaseExport
$query = $this->addDateRange($query, 'invoices');
$query = $this->filterByClients($query);
$query = $this->filterByUserPermissions($query);
$query->cursor()
->each(function ($invoice) {

View File

@ -92,10 +92,13 @@ class ARSummaryReport extends BaseExport
$this->csv->insertOne($this->buildHeader());
Client::query()
$query = Client::query()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->orderBy('balance', 'desc')
->where('is_deleted', 0);
$query = $this->filterByUserPermissions($query);
$query->orderBy('balance', 'desc')
->cursor()
->each(function ($client) {

View File

@ -88,13 +88,16 @@ class ClientBalanceReport extends BaseExport
$this->csv->insertOne($this->buildHeader());
Client::query()
$query = Client::query()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->orderBy('balance', 'desc')
->where('is_deleted', 0);
$query = $this->filterByUserPermissions($query);
$query->orderBy('balance', 'desc')
->cursor()
->each(function ($client) {
/** @var \App\Models\Client $client */
$this->csv->insertOne($this->buildRow($client));
});

View File

@ -90,13 +90,16 @@ class ClientSalesReport extends BaseExport
$this->csv->insertOne($this->buildHeader());
Client::query()
$query = Client::query()
->where('company_id', $this->company->id)
->where('is_deleted', 0)
->orderBy('balance', 'desc')
->where('is_deleted', 0);
$query = $this->filterByUserPermissions($query);
$query->orderBy('balance', 'desc')
->cursor()
->each(function ($client) {
/** @var \App\Models\Client $client */
$this->csv->insertOne($this->buildRow($client));
});

View File

@ -66,6 +66,8 @@ class ProjectReport extends BaseExport
$query = \App\Models\Project::with(['invoices','expenses','tasks'])
->where('company_id', $this->company->id);
$query = $this->filterByUserPermissions($query);
$projects = &$this->input['projects'];
if ($projects) {

View File

@ -89,6 +89,7 @@ class TaxSummaryReport extends BaseExport
->orderBy('balance', 'desc');
$query = $this->addDateRange($query, 'invoices');
$query = $this->filterByUserPermissions($query);
$this->csv->insertOne([ctrans('texts.tax_summary')]);
$this->csv->insertOne([ctrans('texts.created_on'),' ',$this->translateDate(now()->format('Y-m-d'), $this->company->date_format(), $this->company->locale())]);

View File

@ -74,6 +74,8 @@ class UserSalesReport extends BaseExport
$query = $this->filterByClients($query);
$query = $this->filterByUserPermissions($query);
$this->csv->insertOne([ctrans('texts.user_sales_report_header', ['client' => $this->client_description, 'start_date' => $this->start_date, 'end_date' => $this->end_date])]);
if (count($this->input['report_keys']) == 0) {