chore(accounts): remove legacy-era accounts sources

This commit is contained in:
Darko Gjorgjijoski
2026-08-20 23:48:22 +02:00
parent bc213966ff
commit 973fda1df6
42 changed files with 0 additions and 2776 deletions
@@ -1,121 +0,0 @@
<?php
namespace App\Domains\Accounts\Application;
use App\Domains\Accounts\Contracts\CompanyDataPurger;
use App\Domains\Accounts\Contracts\CompanyDefaultsProvisioner;
use App\Domains\Accounts\Models\Company;
use App\Domains\Accounts\Models\CompanySetting;
use Silber\Bouncer\BouncerFacade;
use Silber\Bouncer\Database\Role;
class CompanyService
{
public function __construct(
private readonly CompanyDefaultsProvisioner $companyDefaultsProvisioner,
private readonly CompanyDataPurger $companyDataPurger,
) {}
public function setupDefaults(Company $company, int $currencyId = 13): bool
{
$this->setupRoles($company);
$this->companyDefaultsProvisioner->provision($company);
$this->setupDefaultSettings($company, $currencyId);
return true;
}
public function setupRoles(Company $company): void
{
BouncerFacade::scope()->to($company->id);
$owner = BouncerFacade::role()->firstOrCreate([
'name' => 'owner',
'title' => 'Owner',
'scope' => $company->id,
]);
foreach (config('abilities.abilities') as $ability) {
BouncerFacade::allow($owner)->to($ability['ability'], $ability['model']);
}
}
public function delete(Company $company): bool
{
$this->companyDataPurger->purge($company);
$roles = Role::when($company->id, function ($query) use ($company) {
return $query->where('scope', $company->id);
})->get();
if ($roles) {
$roles->map(function ($role) {
$role->delete();
});
}
$company->users()->detach();
$company->settings()->delete();
$company->delete();
return true;
}
private function setupDefaultSettings(Company $company, int $currencyId): void
{
$defaultInvoiceEmailBody = 'You have received a new invoice from <b>{COMPANY_NAME}</b>.</br> Please download using the button below:';
$defaultEstimateEmailBody = 'You have received a new estimate from <b>{COMPANY_NAME}</b>.</br> Please download using the button below:';
$defaultPaymentEmailBody = 'Thank you for the payment.</b></br> Please download your payment receipt using the button below:';
$billingAddressFormat = '<h3>{BILLING_ADDRESS_NAME}</h3><p>{BILLING_ADDRESS_STREET_1}</p><p>{BILLING_ADDRESS_STREET_2}</p><p>{BILLING_CITY} {BILLING_STATE}</p><p>{BILLING_COUNTRY} {BILLING_ZIP_CODE}</p><p>{BILLING_PHONE}</p>';
$shippingAddressFormat = '<h3>{SHIPPING_ADDRESS_NAME}</h3><p>{SHIPPING_ADDRESS_STREET_1}</p><p>{SHIPPING_ADDRESS_STREET_2}</p><p>{SHIPPING_CITY} {SHIPPING_STATE}</p><p>{SHIPPING_COUNTRY} {SHIPPING_ZIP_CODE}</p><p>{SHIPPING_PHONE}</p>';
$companyAddressFormat = '<h3><strong>{COMPANY_NAME}</strong></h3><p>{COMPANY_ADDRESS_STREET_1}</p><p>{COMPANY_ADDRESS_STREET_2}</p><p>{COMPANY_CITY} {COMPANY_STATE}</p><p>{COMPANY_COUNTRY} {COMPANY_ZIP_CODE}</p><p>{COMPANY_PHONE}</p>';
$paymentFromCustomerAddress = '<h3>{BILLING_ADDRESS_NAME}</h3><p>{BILLING_ADDRESS_STREET_1}</p><p>{BILLING_ADDRESS_STREET_2}</p><p>{BILLING_CITY} {BILLING_STATE} {BILLING_ZIP_CODE}</p><p>{BILLING_COUNTRY}</p><p>{BILLING_PHONE}</p>';
$settings = [
'invoice_mail_body' => $defaultInvoiceEmailBody,
'estimate_mail_body' => $defaultEstimateEmailBody,
'payment_mail_body' => $defaultPaymentEmailBody,
'invoice_company_address_format' => $companyAddressFormat,
'invoice_shipping_address_format' => $shippingAddressFormat,
'invoice_billing_address_format' => $billingAddressFormat,
'estimate_company_address_format' => $companyAddressFormat,
'estimate_shipping_address_format' => $shippingAddressFormat,
'estimate_billing_address_format' => $billingAddressFormat,
'payment_company_address_format' => $companyAddressFormat,
'payment_from_customer_address_format' => $paymentFromCustomerAddress,
'currency' => $currencyId,
'time_zone' => 'Asia/Kolkata',
'language' => 'en',
'fiscal_year' => '1-12',
'carbon_date_format' => 'Y/m/d',
'moment_date_format' => 'YYYY/MM/DD',
'carbon_time_format' => 'H:i',
'moment_time_format' => 'HH:mm',
'invoice_use_time' => 'NO',
'notification_email' => 'noreply@invoiceshelf.com',
'notify_invoice_viewed' => 'NO',
'notify_estimate_viewed' => 'NO',
'tax_per_item' => 'NO',
'discount_per_item' => 'NO',
'invoice_email_attachment' => 'NO',
'estimate_email_attachment' => 'NO',
'payment_email_attachment' => 'NO',
'retrospective_edits' => 'allow',
'invoice_number_format' => '{{SERIES:INV}}{{DELIMITER:-}}{{SEQUENCE:6}}',
'credit_note_number_format' => '{{SERIES:CN}}{{DELIMITER:-}}{{SEQUENCE:6}}',
'estimate_number_format' => '{{SERIES:EST}}{{DELIMITER:-}}{{SEQUENCE:6}}',
'payment_number_format' => '{{SERIES:PAY}}{{DELIMITER:-}}{{SEQUENCE:6}}',
'estimate_set_expiry_date_automatically' => 'YES',
'estimate_expiry_date_days' => 7,
'invoice_set_due_date_automatically' => 'YES',
'invoice_due_date_days' => 7,
'bulk_exchange_rate_configured' => 'YES',
'estimate_convert_action' => 'no_action',
'automatically_expire_public_links' => 'YES',
'link_expiry_days' => 7,
];
CompanySetting::setSettings($settings, $company->id);
}
}
@@ -1,79 +0,0 @@
<?php
namespace App\Domains\Accounts\Application;
use App\Domains\Accounts\Contracts\MemberReferencesCleaner;
use App\Domains\Accounts\Models\User;
use Silber\Bouncer\BouncerFacade;
class MemberService
{
public function __construct(
private readonly MemberReferencesCleaner $memberReferencesCleaner,
) {}
/**
* @param array<string, mixed> $attributes
* @param iterable<int, array{id: int, role: string}> $companies
*/
public function create(array $attributes, iterable $companies): User
{
$user = User::create($attributes);
$user->setSettings([
'language' => 'default',
]);
$companies = collect($companies);
$user->companies()->sync($companies->pluck('id'));
foreach ($companies as $company) {
BouncerFacade::scope()->to($company['id']);
BouncerFacade::sync($user)->roles([$company['role']]);
}
return $user;
}
/**
* @param array<string, mixed> $attributes
* @param iterable<int, array{id: int, role: string}> $companies
*/
public function update(User $user, array $attributes, iterable $companies): User
{
$user->update($attributes);
$companies = collect($companies);
$user->companies()->sync($companies->pluck('id'));
foreach ($companies as $company) {
BouncerFacade::scope()->to($company['id']);
BouncerFacade::sync($user)->roles([$company['role']]);
}
return $user;
}
public function delete(array $ids): bool
{
foreach ($ids as $id) {
$user = User::find($id);
if (! $user) {
continue;
}
$this->memberReferencesCleaner->clear($user);
if ($user->settings()->exists()) {
$user->settings()->delete();
}
$user->delete();
}
return true;
}
}
@@ -1,57 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Controllers\Auth;
use App\Domains\Accounts\Application\InvitationService;
use App\Domains\Accounts\Http\Requests\LoginRequest;
use App\Domains\Accounts\Models\CompanyInvitation;
use App\Domains\Accounts\Models\User;
use App\Platform\Http\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
public function login(LoginRequest $request)
{
$user = User::whereRaw('LOWER(email) = ?', [strtolower($request->username)])->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
// Auto-accept invitation if token is provided
if ($request->has('invitation_token') && $request->invitation_token) {
$invitation = CompanyInvitation::where('token', $request->invitation_token)
->pending()
->first();
if ($invitation) {
app(InvitationService::class)->accept($invitation, $user);
}
}
return response()->json([
'type' => 'Bearer',
'token' => $user->createToken($request->device_name)->plainTextToken,
]);
}
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete();
return response()->json([
'success' => true,
]);
}
public function check()
{
return Auth::check();
}
}
@@ -1,52 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Controllers\Auth;
use App\Platform\Http\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
/**
* Get the response for a successful password reset link.
*
* @param string $response
* @return RedirectResponse|JsonResponse
*/
protected function sendResetLinkResponse(Request $request, $response)
{
return response()->json([
'message' => 'Password reset email sent.',
'data' => $response,
]);
}
/**
* Get the response for a failed password reset link.
*
* @param string $response
* @return RedirectResponse|JsonResponse
*/
protected function sendResetLinkFailedResponse(Request $request, $response)
{
return response()->json([
'error' => 'Email could not be sent to this email address.',
], 403);
}
}
@@ -1,48 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Controllers\Auth;
use App\Platform\Http\Controller;
use App\Providers\AppServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = AppServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
public function logout(Request $request): void
{
auth()->guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
}
}
@@ -1,78 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Controllers\Auth;
use App\Platform\Http\Controller;
use App\Providers\AppServiceProvider;
use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Contracts\Auth\CanResetPassword;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
/**
* Where to redirect users after resetting their password.
*
* @var string
*/
protected $redirectTo = AppServiceProvider::HOME;
/**
* Get the response for a successful password reset.
*
* @param string $response
* @return RedirectResponse|JsonResponse
*/
protected function sendResetResponse(Request $request, $response)
{
return response()->json([
'message' => 'Password reset successfully.',
]);
}
/**
* Reset the given user's password.
*
* @param CanResetPassword $user
* @param string $password
* @return void
*/
protected function resetPassword($user, $password)
{
$user->password = $password;
$user->setRememberToken(Str::random(60));
$user->save();
event(new PasswordReset($user));
}
/**
* Get the response for a failed password reset.
*
* @param string $response
* @return RedirectResponse|JsonResponse
*/
protected function sendResetFailedResponse(Request $request, $response)
{
return response('Failed, Invalid Token.', 403);
}
}
@@ -1,20 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Controllers\Company;
use App\Platform\Http\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class AbilitiesController extends Controller
{
/**
* Handle the incoming request.
*
* @return Response
*/
public function __invoke(Request $request)
{
return response()->json(['abilities' => config('abilities.abilities')]);
}
}
@@ -1,52 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Controllers\Company;
use App\Domains\Accounts\Contracts\CompanyAddressWriter;
use App\Domains\Accounts\Contracts\CompanyLogoManager;
use App\Domains\Accounts\Http\Requests\CompanyLogoRequest;
use App\Domains\Accounts\Http\Requests\CompanyRequest;
use App\Domains\Accounts\Http\Resources\CompanyResource;
use App\Domains\Accounts\Models\Company;
use App\Platform\Http\Controller;
class CompanyController extends Controller
{
public function __construct(
private readonly CompanyAddressWriter $companyAddressWriter,
private readonly CompanyLogoManager $companyLogoManager,
) {}
public function updateCompany(CompanyRequest $request)
{
$company = Company::find($request->header('company'));
$this->authorize('manage company', $company);
$company->update($request->getCompanyPayload());
$this->companyAddressWriter->upsert($company, (array) $request->input('address'));
return new CompanyResource($company);
}
public function uploadCompanyLogo(CompanyLogoRequest $request)
{
$company = Company::find($request->header('company'));
$this->authorize('manage company', $company);
$data = json_decode($request->company_logo);
if (isset($request->is_company_logo_removed) && (bool) $request->is_company_logo_removed) {
$this->companyLogoManager->clear($company);
}
if ($data) {
$this->companyLogoManager->replaceBase64($company, $data->data, $data->name);
}
return response()->json([
'success' => true,
]);
}
}
@@ -1,81 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Controllers\Company;
use App\Domains\Accounts\Http\Requests\GetSettingsRequest;
use App\Domains\Accounts\Http\Requests\UpdateSettingsRequest;
use App\Domains\Accounts\Models\Company;
use App\Domains\Accounts\Models\CompanySetting;
use App\Domains\Accounts\Models\User;
use App\Platform\Http\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Silber\Bouncer\BouncerFacade;
class CompanySettingsController extends Controller
{
public function show(GetSettingsRequest $request): JsonResponse
{
$settings = CompanySetting::getSettings((array) $request->settings, $request->header('company'));
return response()->json($settings);
}
public function update(UpdateSettingsRequest $request): JsonResponse
{
$company = Company::find($request->header('company'));
$this->authorize('manage company', $company);
$data = $request->settings;
if (
Arr::exists($data, 'currency') &&
(CompanySetting::getSetting('currency', $company->id) !== $data['currency']) &&
$company->hasTransactions()
) {
return response()->json([
'success' => false,
'message' => 'Cannot update company currency after transactions are created.',
]);
}
CompanySetting::setSettings($data, $request->header('company'));
return response()->json([
'success' => true,
]);
}
public function checkTransactions(Request $request): JsonResponse
{
$company = Company::find($request->header('company'));
$this->authorize('manage company', $company);
return response()->json([
'has_transactions' => $company->hasTransactions(),
]);
}
public function transferOwnership(Request $request, User $user): JsonResponse
{
$company = Company::find($request->header('company'));
$this->authorize('transfer company ownership', $company);
if (! $user->hasCompany($company->id)) {
return response()->json([
'success' => false,
'message' => 'User does not belong to this company.',
]);
}
$company->update(['owner_id' => $user->id]);
BouncerFacade::scope()->to($company->id);
BouncerFacade::sync($user)->roles(['owner']);
return response()->json([
'success' => true,
]);
}
}
@@ -1,119 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Controllers\Company;
use App\Domains\Accounts\Application\MemberService;
use App\Domains\Accounts\Http\Requests\DeleteMemberRequest;
use App\Domains\Accounts\Http\Requests\MemberRequest;
use App\Domains\Accounts\Http\Resources\UserResource;
use App\Domains\Accounts\Models\User;
use App\Platform\Http\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class MembersController extends Controller
{
public function __construct(
private readonly MemberService $memberService,
) {}
/**
* Display a listing of the resource.
*
* @return JsonResponse
*/
public function index(Request $request)
{
$this->authorize('viewAny', User::class);
$limit = $request->has('limit') ? $request->limit : 10;
$user = $request->user();
$users = User::whereCompany()
->applyFilters($request->all())
->where('id', '<>', $user->id)
->latest()
->paginate($limit);
return UserResource::collection($users)
->additional(['meta' => [
'user_total_count' => User::whereCompany()->count(),
]]);
}
/**
* Store a newly created resource in storage.
*
* @return JsonResponse
*/
public function store(MemberRequest $request)
{
$this->authorize('create', User::class);
$user = $this->memberService->create(
$request->getUserPayload(),
$request->validated('companies'),
);
return new UserResource($user);
}
/**
* Display the specified resource.
*
* @return JsonResponse
*/
public function show(User $member)
{
$this->authorize('view', $member);
return new UserResource($member);
}
/**
* Update the specified resource in storage.
*
* @return JsonResponse
*/
public function update(MemberRequest $request, User $member)
{
$this->authorize('update', $member);
$this->memberService->update(
$member,
$request->getUserPayload(),
$request->validated('companies'),
);
return new UserResource($member);
}
/**
* Display a listing of the resource.
*
* @param Request $request
* @return JsonResponse
*/
public function delete(DeleteMemberRequest $request)
{
$this->authorize('delete multiple users', User::class);
if ($request->users) {
// Scope the candidate ids to members of the acting company so a user
// from one company cannot delete accounts belonging to another.
$ids = User::whereCompany()
->whereIn('id', $request->users)
->pluck('id')
->toArray();
if ($ids) {
$this->memberService->delete($ids);
}
}
return response()->json([
'success' => true,
]);
}
}
@@ -1,120 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Controllers\Company;
use App\Domains\Accounts\Http\Requests\RoleRequest;
use App\Domains\Accounts\Http\Resources\RoleResource;
use App\Domains\Accounts\Models\User;
use App\Platform\Http\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Silber\Bouncer\BouncerFacade;
use Silber\Bouncer\Database\Role;
class RolesController extends Controller
{
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index(Request $request)
{
$this->authorize('viewAny', Role::class);
$roles = Role::when($request->has('orderByField'), function ($query) use ($request) {
return $query->orderBy($request['orderByField'], $request['orderBy']);
})
->when($request->company_id, function ($query) use ($request) {
return $query->where('scope', $request->company_id);
})
->get();
return RoleResource::collection($roles);
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return Response
*/
public function store(RoleRequest $request)
{
$this->authorize('create', Role::class);
$role = Role::create($request->getRolePayload());
$this->syncAbilities($request, $role);
return new RoleResource($role);
}
/**
* Display the specified resource.
*
* @param \Spatie\Permission\Models\Role $role
* @return Response
*/
public function show(Role $role)
{
$this->authorize('view', $role);
return new RoleResource($role);
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @param \Spatie\Permission\Models\Role $role
* @return Response
*/
public function update(RoleRequest $request, Role $role)
{
$this->authorize('update', $role);
$role->update($request->getRolePayload());
$this->syncAbilities($request, $role);
return new RoleResource($role);
}
/**
* Remove the specified resource from storage.
*
* @param \Spatie\Permission\Models\Role $role
* @return Response
*/
public function destroy(Role $role)
{
$this->authorize('delete', $role);
$users = User::whereIs($role->name)->get()->toArray();
if (! empty($users)) {
return respondJson('role_attached_to_users', 'Roles Attached to user');
}
$role->delete();
return response()->json([
'success' => true,
]);
}
private function syncAbilities(RoleRequest $request, $role)
{
foreach (config('abilities.abilities') as $ability) {
$check = array_search($ability['ability'], array_column($request->abilities, 'ability'));
if ($check !== false) {
BouncerFacade::allow($role)->to($ability['ability'], $ability['model']);
} else {
BouncerFacade::disallow($role)->to($ability['ability'], $ability['model']);
}
}
return true;
}
}
@@ -1,22 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Request;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param Request $request
* @return string
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
}
@@ -1,40 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Schema;
use Symfony\Component\HttpFoundation\Response;
class CompanyMiddleware
{
public function handle(Request $request, Closure $next): Response
{
if (Schema::hasTable('user_company')) {
$user = $request->user();
if (! $user) {
return $next($request);
}
$firstCompany = $user->companies()->first();
// User has no companies — allow request through without company header
if (! $firstCompany) {
return $next($request);
}
// Super admin without company header — allow pass-through (admin mode)
if ($user->isSuperAdmin() && ! $request->header('company')) {
return $next($request);
}
if (! $request->header('company') || ! $user->hasCompany($request->header('company'))) {
$request->headers->set('company', $firstCompany->id);
}
}
return $next($request);
}
}
@@ -1,27 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
return $next($request);
}
}
@@ -1,25 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class RedirectIfUnauthorized
{
/**
* Handle an incoming request.
*
* @return mixed
*/
public function handle(Request $request, Closure $next, $guard = null): Response
{
if (Auth::guard($guard)->check()) {
return $next($request);
}
return redirect('/login');
}
}
@@ -1,49 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Silber\Bouncer\Bouncer;
use Symfony\Component\HttpFoundation\Response;
class ScopeBouncer
{
/**
* The Bouncer instance.
*
* @var Bouncer
*/
protected $bouncer;
/**
* Constructor.
*/
public function __construct(Bouncer $bouncer)
{
$this->bouncer = $bouncer;
}
/**
* Set the proper Bouncer scope for the incoming request.
*
* @return mixed
*/
public function handle(Request $request, Closure $next): Response
{
$user = $request->user();
$company = $request->header('company');
if (! $company) {
$firstCompany = $user->companies()->first();
if (! $firstCompany) {
return $next($request);
}
$company = $firstCompany->id;
}
$this->bouncer->scope()->to($company);
return $next($request);
}
}
@@ -1,36 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Requests;
use App\Rules\Base64Mime;
use Illuminate\Foundation\Http\FormRequest;
class AvatarRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'admin_avatar' => [
'nullable',
'file',
'mimes:gif,jpg,png',
'max:20000',
],
'avatar' => [
'nullable',
new Base64Mime(['gif', 'jpg', 'png']),
],
];
}
}
@@ -1,77 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
class CompaniesRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => [
'required',
Rule::unique('companies'),
'string',
],
'currency' => [
'required',
],
'address.name' => [
'nullable',
],
'address.address_street_1' => [
'nullable',
],
'address.address_street_2' => [
'nullable',
],
'address.city' => [
'nullable',
],
'address.state' => [
'nullable',
],
'address.country_id' => [
'required',
],
'address.zip' => [
'nullable',
],
'address.phone' => [
'nullable',
],
'address.fax' => [
'nullable',
],
];
}
public function getCompanyPayload()
{
return collect($this->validated())
->only([
'name',
'vat_id',
'tax_id',
])
->merge([
'owner_id' => $this->user()->id,
'slug' => Str::slug($this->name),
])
->toArray();
}
}
@@ -1,30 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Requests;
use App\Rules\Base64Mime;
use Illuminate\Foundation\Http\FormRequest;
class CompanyLogoRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'company_logo' => [
'nullable',
new Base64Mime(['gif', 'jpg', 'png']),
],
];
}
}
@@ -1,54 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
class CompanyRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => [
'required',
Rule::unique('companies')->ignore($this->header('company'), 'id'),
],
'vat_id' => [
'nullable',
],
'tax_id' => [
'nullable',
],
'address.country_id' => [
'required',
],
];
}
public function getCompanyPayload()
{
return collect($this->validated())
->only([
'name',
'vat_id',
'tax_id',
])
->merge([
'slug' => Str::slug($this->name),
])
->toArray();
}
}
@@ -1,33 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class DeleteMemberRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'users' => [
'required',
],
'users.*' => [
'required',
Rule::exists('users', 'id'),
],
];
}
}
@@ -1,32 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class GetSettingsRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'settings' => [
'required',
],
'settings.*' => [
'required',
'string',
],
];
}
}
@@ -1,34 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class LoginRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'username' => [
'required',
],
'password' => [
'required',
],
'device_name' => [
'required',
],
];
}
}
@@ -1,80 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Requests;
use App\Rules\IdnEmail;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class MemberRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
$rules = [
'name' => [
'required',
],
'email' => [
'required',
new IdnEmail,
Rule::unique('users'),
],
'phone' => [
'nullable',
],
'password' => [
'required',
'min:8',
],
'companies' => [
'required',
],
'companies.*.id' => [
'required',
],
'companies.*.role' => [
'required',
],
];
if ($this->getMethod() == 'PUT') {
$rules['email'] = [
'required',
new IdnEmail,
Rule::unique('users')->ignore($this->member),
];
$rules['password'] = [
'nullable',
'min:8',
];
}
return $rules;
}
public function getUserPayload()
{
return collect($this->validated())
->only([
'name',
'email',
'phone',
'password',
])
->merge([
'creator_id' => $this->user()->id,
])
->toArray();
}
}
@@ -1,40 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Requests;
use App\Rules\IdnEmail;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
class ProfileRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'name' => [
'required',
],
'password' => [
'nullable',
'min:8',
],
'email' => [
'required',
new IdnEmail,
Rule::unique('users')->ignore(Auth::id(), 'id'),
],
];
}
}
@@ -1,58 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class RoleRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
$rules = [
'name' => [
'required',
'string',
Rule::unique('roles')->where('scope', $this->header('company')),
],
'abilities' => [
'required',
],
'abilities.*' => [
'required',
],
];
if ($this->getMethod() == 'PUT') {
$rules['name'] = [
'required',
'string',
Rule::unique('roles')
->ignore($this->route('role')->id, 'id')
->where('scope', $this->header('company')),
];
}
return $rules;
}
public function getRolePayload()
{
return collect($this->except('abilities'))
->merge([
'scope' => $this->header('company'),
])
->toArray();
}
}
@@ -1,28 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateSettingsRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
'settings' => [
'required',
],
];
}
}
@@ -1,58 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Resources;
use App\Domains\Contacts\Http\Resources\AddressResource;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class CompanyResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param Request $request
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'vat_id' => $this->vat_id,
'tax_id' => $this->tax_id,
'logo' => $this->logo,
'logo_path' => $this->logo_path,
'unique_hash' => $this->unique_hash,
'owner_id' => $this->owner_id,
'slug' => $this->slug,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'address' => $this->when($this->address()->exists(), function () {
return new AddressResource($this->address);
}),
'owner' => $this->when($this->relationLoaded('owner'), function () {
return new UserResource($this->owner);
}),
'roles' => RoleResource::collection($this->roles),
'user_role' => $this->getUserRoleTitle(),
];
}
private function getUserRoleTitle(): ?string
{
$user = Auth::user();
if (! $user) {
return null;
}
return DB::table('assigned_roles')
->join('roles', 'roles.id', '=', 'assigned_roles.role_id')
->where('assigned_roles.entity_id', $user->id)
->where('assigned_roles.entity_type', $user->getMorphClass())
->where('assigned_roles.scope', $this->id)
->value('roles.title');
}
}
@@ -1,31 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Resources\CustomerPortal;
use App\Domains\Contacts\Http\Resources\CustomerPortal\AddressResource;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class CompanyResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param Request $request
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'slug' => $this->slug,
'logo' => $this->logo,
'logo_path' => $this->logo_path,
'unique_hash' => $this->unique_hash,
'owner_id' => $this->owner_id,
'address' => $this->when($this->address()->exists(), function () {
return new AddressResource($this->address);
}),
];
}
}
@@ -1,46 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Resources\CustomerPortal;
use App\Domains\Money\Http\Resources\CustomerPortal\CurrencyResource;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param Request $request
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'phone' => $this->phone,
'role' => $this->role,
'contact_name' => $this->contact_name,
'company_name' => $this->company_name,
'website' => $this->website,
'enable_portal' => $this->enable_portal,
'currency_id' => $this->currency_id,
'facebook_id' => $this->facebook_id,
'google_id' => $this->google_id,
'github_id' => $this->github_id,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'avatar' => $this->avatar,
'is_owner' => $this->isOwner(),
'roles' => $this->roles,
'formatted_created_at' => $this->formattedCreatedAt,
'currency' => $this->when($this->currency()->exists(), function () {
return new CurrencyResource($this->currency);
}),
'companies' => $this->when($this->companies()->exists(), function () {
return CompanyResource::collection($this->companies);
}),
];
}
}
@@ -1,35 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Resources;
use App\Domains\Accounts\Models\CompanySetting;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class RoleResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param Request $request
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'title' => $this->title,
'level' => $this->level,
'formatted_created_at' => $this->getFormattedAt(),
'abilities' => $this->getAbilities(),
];
}
public function getFormattedAt()
{
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->scope);
return Carbon::parse($this->created_at)->translatedFormat($dateFormat);
}
}
@@ -1,47 +0,0 @@
<?php
namespace App\Domains\Accounts\Http\Resources;
use App\Domains\Money\Http\Resources\CurrencyResource;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param Request $request
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'phone' => $this->phone,
'role' => $this->role,
'contact_name' => $this->contact_name,
'company_name' => $this->company_name,
'website' => $this->website,
'enable_portal' => $this->enable_portal,
'currency_id' => $this->currency_id,
'facebook_id' => $this->facebook_id,
'google_id' => $this->google_id,
'github_id' => $this->github_id,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'avatar' => $this->avatar,
'is_owner' => $this->isOwner(),
'is_super_admin' => $this->isSuperAdmin(),
'roles' => $this->roles,
'formatted_created_at' => $this->formattedCreatedAt,
'currency' => $this->when($this->currency()->exists(), function () {
return new CurrencyResource($this->currency);
}),
'companies' => $this->when($this->companies()->exists(), function () {
return CompanyResource::collection($this->companies);
}),
];
}
}
-201
View File
@@ -1,201 +0,0 @@
<?php
namespace App\Domains\Accounts\Models;
use App\Domains\Catalog\Models\Item;
use App\Domains\Catalog\Models\Unit;
use App\Domains\Contacts\Models\Address;
use App\Domains\Contacts\Models\Customer;
use App\Domains\Metadata\Models\CustomField;
use App\Domains\Metadata\Models\CustomFieldValue;
use App\Domains\Money\Models\ExchangeRateLog;
use App\Domains\Money\Models\ExchangeRateProvider;
use App\Domains\Purchases\Models\Expense;
use App\Domains\Purchases\Models\ExpenseCategory;
use App\Domains\Receivables\Models\Payment;
use App\Domains\Receivables\Models\PaymentMethod;
use App\Domains\Sales\Models\Estimate;
use App\Domains\Sales\Models\Invoice;
use App\Domains\Sales\Models\RecurringInvoice;
use App\Domains\Taxation\Models\TaxType;
use App\Platform\Storage\Models\FileDisk;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Silber\Bouncer\Database\Role;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class Company extends Model implements HasMedia
{
protected $table = 'companies';
use HasFactory;
use InteractsWithMedia;
public function registerMediaCollections(): void
{
$this->addMediaCollection('logo')
->useDisk('public')
->singleFile();
}
protected $guarded = [
'id',
];
protected $appends = ['logo', 'logo_path'];
public function getRolesAttribute()
{
return Role::where('scope', $this->id)
->get();
}
public function getLogoPathAttribute()
{
$logo = $this->getMedia('logo')->first();
$isSystem = FileDisk::whereSetAsDefault(true)->first()->isSystem();
if ($logo) {
if ($isSystem) {
return $logo->getPath();
} else {
return $logo->getFullUrl();
}
}
return null;
}
public function getLogoAttribute()
{
$logo = $this->getMedia('logo')->first();
if ($logo) {
return $logo->getFullUrl();
}
return null;
}
public function customers(): HasMany
{
return $this->hasMany(Customer::class);
}
public function owner(): BelongsTo
{
return $this->belongsTo(User::class, 'owner_id');
}
public function settings(): HasMany
{
return $this->hasMany(CompanySetting::class);
}
public function recurringInvoices(): HasMany
{
return $this->hasMany(RecurringInvoice::class);
}
public function customFields(): HasMany
{
return $this->hasMany(CustomField::class);
}
public function customFieldValues(): HasMany
{
return $this->hasMany(CustomFieldValue::class);
}
public function exchangeRateLogs(): HasMany
{
return $this->hasMany(ExchangeRateLog::class);
}
public function exchangeRateProviders(): HasMany
{
return $this->hasMany(ExchangeRateProvider::class);
}
public function invoices(): HasMany
{
return $this->hasMany(Invoice::class);
}
public function expenses(): HasMany
{
return $this->hasMany(Expense::class);
}
public function units(): HasMany
{
return $this->hasMany(Unit::class);
}
public function expenseCategories(): HasMany
{
return $this->hasMany(ExpenseCategory::class);
}
public function taxTypes(): HasMany
{
return $this->hasMany(TaxType::class);
}
public function items(): HasMany
{
return $this->hasMany(Item::class);
}
public function payments(): HasMany
{
return $this->hasMany(Payment::class);
}
public function paymentMethods(): HasMany
{
return $this->hasMany(PaymentMethod::class);
}
public function estimates(): HasMany
{
return $this->hasMany(Estimate::class);
}
public function address(): HasOne
{
return $this->hasOne(Address::class);
}
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class, 'user_company', 'company_id', 'user_id');
}
/**
* Check whether the company has any business data such as customers,
* items, invoices, estimates, expenses, payments, or recurring invoices.
*/
public function hasTransactions(): bool
{
if (
$this->customers()->exists() ||
$this->items()->exists() ||
$this->invoices()->exists() ||
$this->estimates()->exists() ||
$this->expenses()->exists() ||
$this->payments()->exists() ||
$this->recurringInvoices()->exists()
) {
return true;
}
return false;
}
}
@@ -1,82 +0,0 @@
<?php
namespace App\Domains\Accounts\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Collection;
class CompanySetting extends Model
{
protected $table = 'company_settings';
use HasFactory;
protected $fillable = ['company_id', 'option', 'value'];
public function company(): BelongsTo
{
return $this->belongsTo(Company::class);
}
public function scopeWhereCompany($query, $company_id)
{
$query->where('company_id', $company_id);
}
/**
* Bulk create or update settings for a specific company.
*/
public static function setSettings(array $settings, mixed $company_id): void
{
foreach ($settings as $key => $value) {
self::updateOrCreate(
[
'option' => $key,
'company_id' => $company_id,
],
[
'option' => $key,
'company_id' => $company_id,
'value' => $value,
]
);
}
}
/**
* Retrieve all settings for a company as a key-value collection.
*/
public static function getAllSettings(mixed $company_id): Collection
{
return static::whereCompany($company_id)->get()->mapWithKeys(function ($item) {
return [$item['option'] => $item['value']];
});
}
/**
* Retrieve specific settings for a company as a key-value collection.
*/
public static function getSettings(array $settings, mixed $company_id): Collection
{
return static::whereIn('option', $settings)->whereCompany($company_id)
->get()->mapWithKeys(function ($item) {
return [$item['option'] => $item['value']];
});
}
/**
* Retrieve a single company setting value by key, or null if not found.
*/
public static function getSetting(string $key, mixed $company_id): mixed
{
$setting = static::whereOption($key)->whereCompany($company_id)->first();
if ($setting) {
return $setting->value;
} else {
return null;
}
}
}
-414
View File
@@ -1,414 +0,0 @@
<?php
namespace App\Domains\Accounts\Models;
use App\Domains\Accounts\Notifications\MailResetPasswordNotification;
use App\Domains\Catalog\Models\Item;
use App\Domains\Contacts\Models\Address;
use App\Domains\Contacts\Models\Customer;
use App\Domains\Metadata\Concerns\HasCustomFields;
use App\Domains\Money\Models\Currency;
use App\Domains\Purchases\Models\Expense;
use App\Domains\Receivables\Models\Payment;
use App\Domains\Sales\Models\Estimate;
use App\Domains\Sales\Models\Invoice;
use App\Domains\Sales\Models\RecurringInvoice;
use App\Support\SafeOrderBy;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Schema;
use Laravel\Sanctum\HasApiTokens;
use Silber\Bouncer\Database\HasRolesAndAbilities;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class User extends Authenticatable implements HasMedia
{
protected $table = 'users';
use HasApiTokens;
use HasCustomFields;
use HasFactory;
use HasRolesAndAbilities;
use InteractsWithMedia;
use Notifiable;
public function registerMediaCollections(): void
{
$this->addMediaCollection('admin_avatar')
->useDisk('public')
->singleFile();
}
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $guarded = [
'id',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
protected $with = [
'currency',
];
protected $appends = [
'formattedCreatedAt',
'avatar',
];
/**
* Find the user instance for the given username.
*/
public function findForPassport(string $username): ?self
{
return $this->where('email', $username)->first();
}
public function setPasswordAttribute(string $value): void
{
if ($value != null) {
$this->attributes['password'] = bcrypt($value);
}
}
public function isSuperAdmin(): bool
{
return $this->role === 'super admin';
}
public function isSuperAdminOrAdmin(): bool
{
return ($this->role == 'super admin') || ($this->role == 'admin');
}
public static function login(object $request): bool
{
$remember = $request->remember;
$email = $request->email;
$password = $request->password;
return \Auth::attempt(['email' => $email, 'password' => $password], $remember);
}
public function getFormattedCreatedAtAttribute($value)
{
$companyId = request()->header('company');
if (! $companyId || ! CompanySetting::where('company_id', $companyId)->exists()) {
$firstCompany = $this->companies()->first();
if (! $firstCompany) {
return Carbon::parse($this->created_at)->format('Y-m-d');
}
$companyId = $firstCompany->id;
}
$dateFormat = CompanySetting::getSetting('carbon_date_format', $companyId);
return Carbon::parse($this->created_at)->format($dateFormat);
}
public function estimates(): HasMany
{
return $this->hasMany(Estimate::class, 'creator_id');
}
public function customers(): HasMany
{
return $this->hasMany(Customer::class, 'creator_id');
}
public function recurringInvoices(): HasMany
{
return $this->hasMany(RecurringInvoice::class, 'creator_id');
}
public function currency(): BelongsTo
{
return $this->belongsTo(Currency::class, 'currency_id');
}
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'creator_id');
}
public function companies(): BelongsToMany
{
return $this->belongsToMany(Company::class, 'user_company', 'user_id', 'company_id');
}
public function expenses(): HasMany
{
return $this->hasMany(Expense::class, 'creator_id');
}
public function payments(): HasMany
{
return $this->hasMany(Payment::class, 'creator_id');
}
public function invoices(): HasMany
{
return $this->hasMany(Invoice::class, 'creator_id');
}
public function items(): HasMany
{
return $this->hasMany(Item::class, 'creator_id');
}
public function settings(): HasMany
{
return $this->hasMany(UserSetting::class, 'user_id');
}
public function addresses(): HasMany
{
return $this->hasMany(Address::class);
}
public function billingAddress(): HasOne
{
return $this->hasOne(Address::class)->where('type', Address::BILLING_TYPE);
}
public function shippingAddress(): HasOne
{
return $this->hasOne(Address::class)->where('type', Address::SHIPPING_TYPE);
}
/**
* Override the mail body for reset password notification mail.
*/
public function sendPasswordResetNotification($token)
{
$this->notify(new MailResetPasswordNotification($token));
}
public function scopeWhereOrder($query, $orderByField, $orderBy)
{
SafeOrderBy::apply($query, $orderByField, $orderBy);
}
public function scopeWhereSearch($query, $search)
{
foreach (explode(' ', $search) as $term) {
$query->where(function ($query) use ($term) {
$query->where('name', 'LIKE', '%'.$term.'%')
->orWhere('email', 'LIKE', '%'.$term.'%')
->orWhere('phone', 'LIKE', '%'.$term.'%');
});
}
}
public function scopeWhereContactName($query, $contactName)
{
return $query->where('contact_name', 'LIKE', '%'.$contactName.'%');
}
public function scopeWhereDisplayName($query, $displayName)
{
return $query->where('name', 'LIKE', '%'.$displayName.'%');
}
public function scopeWherePhone($query, $phone)
{
return $query->where('phone', 'LIKE', '%'.$phone.'%');
}
public function scopeWhereEmail($query, $email)
{
return $query->where('email', 'LIKE', '%'.$email.'%');
}
public function scopeWhereCompany($query)
{
return $query->whereHas('companies', function ($q) {
$q->where('company_id', request()->header('company'));
});
}
public function scopePaginateData($query, $limit)
{
if ($limit == 'all') {
return $query->get();
}
return $query->paginate($limit);
}
public function scopeApplyFilters($query, array $filters)
{
$filters = collect($filters);
if ($filters->get('search')) {
$query->whereSearch($filters->get('search'));
}
if ($filters->get('display_name')) {
$query->whereDisplayName($filters->get('display_name'));
}
if ($filters->get('email')) {
$query->whereEmail($filters->get('email'));
}
if ($filters->get('phone')) {
$query->wherePhone($filters->get('phone'));
}
if ($filters->get('role')) {
$query->whereHas('roles', function ($q) use ($filters) {
$q->where('roles.id', $filters->get('role'));
});
}
if ($filters->get('orderByField') || $filters->get('orderBy')) {
$field = $filters->get('orderByField') ? $filters->get('orderByField') : 'name';
$orderBy = $filters->get('orderBy') ? $filters->get('orderBy') : 'asc';
$query->whereOrder($field, $orderBy);
}
}
public function scopeWhereSuperAdmin($query)
{
$query->orWhere('role', 'super admin');
}
public function scopeApplyInvoiceFilters($query, array $filters)
{
$filters = collect($filters);
if ($filters->get('from_date') && $filters->get('to_date')) {
$start = Carbon::createFromFormat('Y-m-d', $filters->get('from_date'));
$end = Carbon::createFromFormat('Y-m-d', $filters->get('to_date'));
$query->invoicesBetween($start, $end);
}
}
public function scopeInvoicesBetween($query, $start, $end)
{
$query->whereHas('invoices', function ($query) use ($start, $end) {
$query->whereBetween(
'invoice_date',
[$start->format('Y-m-d'), $end->format('Y-m-d')]
);
});
}
public function getAvatarAttribute()
{
$avatar = $this->getMedia('admin_avatar')->first();
if ($avatar) {
return asset($avatar->getUrl());
}
return 0;
}
/**
* Bulk upsert user settings, creating or updating each key-value pair.
*/
public function setSettings(array $settings): void
{
foreach ($settings as $key => $value) {
$this->settings()->updateOrCreate(
[
'key' => $key,
],
[
'key' => $key,
'value' => $value,
]
);
}
}
public function hasCompany(int $company_id): bool
{
$companies = $this->companies()->pluck('company_id')->toArray();
return in_array($company_id, $companies);
}
public function getAllSettings(): Collection
{
return $this->settings()->get()->mapWithKeys(function ($item) {
return [$item['key'] => $item['value']];
});
}
public function getSettings(array $settings): Collection
{
return $this->settings()->whereIn('key', $settings)->get()->mapWithKeys(function ($item) {
return [$item['key'] => $item['value']];
});
}
/**
* Determine whether the user is the owner of the current company.
*/
public function isOwner(): bool
{
if (Schema::hasColumn('companies', 'owner_id')) {
$company = Company::find(request()->header('company'));
if ($company && $this->id == $company->owner_id) {
return true;
}
} else {
return $this->role == 'super admin' || $this->role == 'admin';
}
return false;
}
/**
* Check whether the user has the required permissions based on ability data,
* considering super-admin status, company ownership, and Bouncer abilities.
*/
public function checkAccess(object $data): bool
{
if (! empty($data->data['super_admin_only']) && $data->data['super_admin_only']) {
return $this->isSuperAdmin();
}
if ($this->isOwner()) {
return true;
}
if ((! $data->data['owner_only']) && empty($data->data['ability'])) {
return true;
}
if ((! $data->data['owner_only']) && (! empty($data->data['ability'])) && (! empty($data->data['model'])) && $this->can($data->data['ability'], $data->data['model'])) {
return true;
}
if ((! $data->data['owner_only']) && $this->can($data->data['ability'])) {
return true;
}
return false;
}
}
@@ -1,21 +0,0 @@
<?php
namespace App\Domains\Accounts\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class UserSetting extends Model
{
protected $table = 'user_settings';
use HasFactory;
protected $guarded = ['id'];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
@@ -1,62 +0,0 @@
<?php
namespace App\Domains\Accounts\Notifications;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class MailResetPasswordNotification extends ResetPassword
{
use Queueable;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct($token)
{
parent::__construct($token);
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
*/
public function via($notifiable): array
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*/
public function toMail($notifiable): MailMessage
{
$link = url('/reset-password/'.$this->token);
return (new MailMessage)
->subject('Reset Password Notification')
->line('Hello! You are receiving this email because we received a password reset request for your account.')
->action('Reset Password', $link)
->line('This password reset link will expire in '.config('auth.passwords.users.expire').' minutes')
->line('If you did not request a password reset, no further action is required.');
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
*/
public function toArray($notifiable): array
{
return [
//
];
}
}
@@ -1,39 +0,0 @@
<?php
namespace App\Domains\Accounts\Policies;
use App\Domains\Accounts\Models\Company;
use App\Domains\Accounts\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class CompanyPolicy
{
use HandlesAuthorization;
public function create(User $user): bool
{
if ($user->isOwner()) {
return true;
}
return false;
}
public function delete(User $user, Company $company): bool
{
if ($user->id == $company->owner_id) {
return true;
}
return false;
}
public function transferOwnership(User $user, Company $company)
{
if ($user->id == $company->owner_id) {
return true;
}
return false;
}
}
@@ -1,20 +0,0 @@
<?php
namespace App\Domains\Accounts\Policies;
use App\Domains\Accounts\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class OwnerPolicy
{
use HandlesAuthorization;
public function managedByOwner(User $user)
{
if ($user->isOwner()) {
return true;
}
return false;
}
}
@@ -1,68 +0,0 @@
<?php
namespace App\Domains\Accounts\Policies;
use App\Domains\Accounts\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
use Silber\Bouncer\Database\Role;
class RolePolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->isOwner();
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Role $role): bool
{
return $user->isOwner();
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->isOwner();
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Role $role): bool
{
return $user->isOwner();
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Role $role): bool
{
return $user->isOwner();
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Role $role): bool
{
return $user->isOwner();
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Role $role): bool
{
return $user->isOwner();
}
}
@@ -1,21 +0,0 @@
<?php
namespace App\Domains\Accounts\Policies;
use App\Domains\Accounts\Models\Company;
use App\Domains\Accounts\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class SettingsPolicy
{
use HandlesAuthorization;
public function manageCompany(User $user, Company $company)
{
if ($user->id == $company->owner_id) {
return true;
}
return false;
}
}
@@ -1,139 +0,0 @@
<?php
namespace App\Domains\Accounts\Policies;
use App\Domains\Accounts\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class UserPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*
* @return mixed
*/
public function viewAny(User $user): bool
{
if ($user->isOwner()) {
return true;
}
return false;
}
/**
* Determine whether the user can view the model.
*
* @return mixed
*/
public function view(User $user, User $model): bool
{
return $user->isOwner() && $this->sharesActiveCompany($model);
}
/**
* Determine whether the user can create models.
*
* @return mixed
*/
public function create(User $user): bool
{
if ($user->isOwner()) {
return true;
}
return false;
}
/**
* Determine whether the user can update the model.
*
* @return mixed
*/
public function update(User $user, User $model): bool
{
return $user->isOwner() && $this->sharesActiveCompany($model);
}
/**
* Determine whether the user can delete the model.
*
* @return mixed
*/
public function delete(User $user, User $model): bool
{
return $user->isOwner() && $this->sharesActiveCompany($model);
}
/**
* Determine whether the user can restore the model.
*
* @return mixed
*/
public function restore(User $user, User $model): bool
{
if ($user->isOwner()) {
return true;
}
return false;
}
/**
* Determine whether the user can permanently delete the model.
*
* @return mixed
*/
public function forceDelete(User $user, User $model): bool
{
if ($user->isOwner()) {
return true;
}
return false;
}
/**
* Determine whether the user can invite the model.
*
* @return mixed
*/
public function invite(User $user, User $model)
{
if ($user->isOwner()) {
return true;
}
return false;
}
/**
* Determine whether the user can delete models.
*
* @return mixed
*/
public function deleteMultiple(User $user)
{
if ($user->isOwner()) {
return true;
}
return false;
}
/**
* A company owner may only act on members of the company set in the
* `company` request header. Without this, view/update/delete resolve the
* target user by global id, which would let an owner of one company read
* or overwrite users belonging to another company (cross-tenant IDOR).
*/
private function sharesActiveCompany(User $model): bool
{
$companyId = request()->header('company');
return $companyId
&& $model->companies()->wherePivot('company_id', $companyId)->exists();
}
}