Rename controller namespaces: drop V1 prefix, clarify roles

V1/Admin     -> Company       (company-scoped controllers)
V1/SuperAdmin -> Admin        (platform-wide admin controllers)
V1/Customer  -> CustomerPortal (customer-facing portal)
V1/Installation -> Setup      (installation wizard)
V1/PDF       -> Pdf           (consistent casing)
V1/Modules   -> Modules       (drop V1 prefix)
V1/Webhook   -> Webhook       (drop V1 prefix)

The V1 prefix served no purpose - API versioning is in the route prefix
(/api/v1/), not the controller namespace. "Admin" was misleading for
company-scoped controllers. "SuperAdmin" is now simply "Admin" for
platform administration.
This commit is contained in:
Darko Gjorgjijoski
2026-04-03 19:15:20 +02:00
parent 0aaf0419c3
commit 64c481e963
129 changed files with 236 additions and 236 deletions

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers\Company\Auth;
use App\Http\Controllers\Controller;
use App\Providers\AppServiceProvider;
use Illuminate\Foundation\Auth\ConfirmsPasswords;
class ConfirmPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Confirm Password Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password confirmations and
| uses a simple trait to include the behavior. You're free to explore
| this trait and override any functions that require customization.
|
*/
use ConfirmsPasswords;
/**
* Where to redirect users when the intended url fails.
*
* @var string
*/
protected $redirectTo = AppServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers\Company\Auth;
use App\Http\Controllers\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);
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Http\Controllers\Company\Auth;
use App\Http\Controllers\Controller;
use App\Providers\AppServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
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');
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace App\Http\Controllers\Company\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Providers\AppServiceProvider;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Validator;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after registration.
*
* @var string
*/
protected $redirectTo = AppServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
}
/**
* Create a new user instance after a valid registration.
*
* @return \App\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => $data['password'],
]);
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace App\Http\Controllers\Company\Auth;
use App\Http\Controllers\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);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Company\Auth;
use App\Http\Controllers\Controller;
use App\Providers\AppServiceProvider;
use Illuminate\Foundation\Auth\VerifiesEmails;
class VerificationController extends Controller
{
/*
|--------------------------------------------------------------------------
| Email Verification Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling email verification for any
| user that recently registered with the application. Emails may also
| be re-sent if the user didn't receive the original email message.
|
*/
use VerifiesEmails;
/**
* Where to redirect users after verification.
*
* @var string
*/
protected $redirectTo = AppServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace App\Http\Controllers\Company\Company;
use App\Facades\Hashids;
use App\Http\Controllers\Controller;
use App\Http\Requests\CompaniesRequest;
use App\Http\Resources\CompanyResource;
use App\Models\Company;
use App\Models\User;
use App\Services\CompanyService;
use Illuminate\Http\Request;
use Silber\Bouncer\BouncerFacade;
class CompaniesController extends Controller
{
public function __construct(
private readonly CompanyService $companyService,
) {}
public function show(Request $request)
{
$company = Company::find($request->header('company'));
return new CompanyResource($company);
}
public function store(CompaniesRequest $request)
{
$this->authorize('create company');
$user = $request->user();
$company = Company::create($request->getCompanyPayload());
$company->unique_hash = Hashids::connection(Company::class)->encode($company->id);
$company->save();
$this->companyService->setupDefaults($company);
$user->companies()->attach($company->id);
$user->assign('super admin');
if ($request->address) {
$company->address()->create($request->address);
}
return new CompanyResource($company);
}
public function destroy(Request $request)
{
$company = Company::find($request->header('company'));
$this->authorize('delete company', $company);
$user = $request->user();
if ($request->name !== $company->name) {
return respondJson('company_name_must_match_with_given_name', 'Company name must match with given name');
}
if ($user->loadCount('companies')->companies_count <= 1) {
return respondJson('You_cannot_delete_all_companies', 'You cannot delete all companies');
}
$this->companyService->delete($company, $user);
return response()->json([
'success' => true,
]);
}
public function transferOwnership(Request $request, User $user)
{
$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::sync($user)->roles(['super admin']);
return response()->json([
'success' => true,
]);
}
public function getUserCompanies(Request $request)
{
$companies = $request->user()->companies;
return CompanyResource::collection($companies);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\Company\Config;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class FiscalYearsController extends Controller
{
/**
* Handle the incoming request.
*
* @return Response
*/
public function __invoke(Request $request)
{
return response()->json([
'fiscal_years' => config('invoiceshelf.fiscal_years'),
]);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\Company\Config;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class LanguagesController extends Controller
{
/**
* Handle the incoming request.
*
* @return Response
*/
public function __invoke(Request $request)
{
return response()->json([
'languages' => config('invoiceshelf.languages'),
]);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\Company\Config;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class RetrospectiveEditsController extends Controller
{
/**
* Handle the incoming request.
*
* @return Response
*/
public function __invoke(Request $request)
{
return response()->json([
'retrospective_edits' => config('invoiceshelf.retrospective_edits'),
]);
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace App\Http\Controllers\Company\CustomField;
use App\Http\Controllers\Controller;
use App\Http\Requests\CustomFieldRequest;
use App\Http\Resources\CustomFieldResource;
use App\Models\CustomField;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class CustomFieldsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index(Request $request)
{
$this->authorize('viewAny', CustomField::class);
$limit = $request->has('limit') ? $request->limit : 5;
$customFields = CustomField::applyFilters($request->all())
->whereCompany()
->latest()
->paginateData($limit);
return CustomFieldResource::collection($customFields);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\CustomFieldRequest $request
* @return Response
*/
public function store(CustomFieldRequest $request)
{
$this->authorize('create', CustomField::class);
$customField = CustomField::createCustomField($request);
return new CustomFieldResource($customField);
}
/**
* Display the specified resource.
*
* @param int $id
* @return Response
*/
public function show(CustomField $customField)
{
$this->authorize('view', $customField);
return new CustomFieldResource($customField);
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @param int $id
* @return Response
*/
public function update(CustomFieldRequest $request, CustomField $customField)
{
$this->authorize('update', $customField);
$customField->updateCustomField($request);
return new CustomFieldResource($customField);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return Response
*/
public function destroy(CustomField $customField)
{
$this->authorize('delete', $customField);
if ($customField->customFieldValues()->exists()) {
$customField->customFieldValues()->delete();
}
$customField->forceDelete();
return response()->json([
'success' => true,
]);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Http\Controllers\Company\Customer;
use App\Http\Controllers\Controller;
use App\Http\Resources\CustomerResource;
use App\Models\Customer;
use App\Services\CustomerService;
use Illuminate\Http\Request;
class CustomerStatsController extends Controller
{
public function __construct(
private readonly CustomerService $customerService,
) {}
public function __invoke(Request $request, Customer $customer)
{
$this->authorize('view', $customer);
$chartData = $this->customerService->getStats(
$customer,
$request->header('company'),
$request->has('previous_year')
);
$customer = Customer::find($customer->id);
return (new CustomerResource($customer))
->additional(['meta' => [
'chartData' => $chartData,
]]);
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace App\Http\Controllers\Company\Customer;
use App\Http\Controllers\Controller;
use App\Http\Requests;
use App\Http\Requests\DeleteCustomersRequest;
use App\Http\Resources\CustomerResource;
use App\Models\Customer;
use App\Services\CustomerService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class CustomersController extends Controller
{
public function __construct(
private readonly CustomerService $customerService,
) {}
/**
* Display a listing of the resource.
*
* @return JsonResponse
*/
public function index(Request $request)
{
$this->authorize('viewAny', Customer::class);
$limit = $request->has('limit') ? $request->limit : 10;
$customers = Customer::with('creator')
->whereCompany()
->applyFilters($request->all())
->withSum('invoices as base_due_amount', 'base_due_amount')
->withSum('invoices as due_amount', 'due_amount')
->paginateData($limit);
return CustomerResource::collection($customers)
->additional(['meta' => [
'customer_total_count' => Customer::whereCompany()->count(),
]]);
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return JsonResponse
*/
public function store(Requests\CustomerRequest $request)
{
$this->authorize('create', Customer::class);
$customer = $this->customerService->create($request);
return new CustomerResource($customer);
}
/**
* Display the specified resource.
*
* @return JsonResponse
*/
public function show(Customer $customer)
{
$this->authorize('view', $customer);
return new CustomerResource($customer);
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @return JsonResponse
*/
public function update(Requests\CustomerRequest $request, Customer $customer)
{
$this->authorize('update', $customer);
$customer = $this->customerService->update($request, $customer);
return new CustomerResource($customer);
}
/**
* Remove a list of Customers along side all their resources (ie. Estimates, Invoices, Payments and Addresses)
*
* @param Request $request
* @return JsonResponse
*/
public function delete(DeleteCustomersRequest $request)
{
$this->authorize('delete multiple customers');
$ids = Customer::whereCompany()
->whereIn('id', $request->ids)
->pluck('id');
$this->customerService->delete($ids);
return response()->json([
'success' => true,
]);
}
}

View File

@@ -0,0 +1,167 @@
<?php
namespace App\Http\Controllers\Company\Dashboard;
use App\Http\Controllers\Controller;
use App\Models\Company;
use App\Models\CompanySetting;
use App\Models\Customer;
use App\Models\Estimate;
use App\Models\Expense;
use App\Models\Invoice;
use App\Models\Payment;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Silber\Bouncer\BouncerFacade;
class DashboardController extends Controller
{
/**
* Handle the incoming request.
*
* @return JsonResponse
*/
public function __invoke(Request $request)
{
$company = Company::find($request->header('company'));
$this->authorize('view dashboard', $company);
$invoice_totals = [];
$expense_totals = [];
$receipt_totals = [];
$net_income_totals = [];
$i = 0;
$months = [];
$monthCounter = 0;
$fiscalYear = CompanySetting::getSetting('fiscal_year', $request->header('company'));
$startDate = Carbon::now();
$start = Carbon::now();
$end = Carbon::now();
$terms = explode('-', $fiscalYear);
$companyStartMonth = intval($terms[0]);
if ($companyStartMonth <= $start->month) {
$startDate->month($companyStartMonth)->startOfMonth();
$start->month($companyStartMonth)->startOfMonth();
$end->month($companyStartMonth)->endOfMonth();
} else {
$startDate->subYear()->month($companyStartMonth)->startOfMonth();
$start->subYear()->month($companyStartMonth)->startOfMonth();
$end->subYear()->month($companyStartMonth)->endOfMonth();
}
if ($request->has('previous_year')) {
$startDate->subYear()->startOfMonth();
$start->subYear()->startOfMonth();
$end->subYear()->endOfMonth();
}
while ($monthCounter < 12) {
array_push(
$invoice_totals,
Invoice::whereBetween(
'invoice_date',
[$start->format('Y-m-d'), $end->format('Y-m-d')]
)
->whereCompany()
->sum('base_total')
);
array_push(
$expense_totals,
Expense::whereBetween(
'expense_date',
[$start->format('Y-m-d'), $end->format('Y-m-d')]
)
->whereCompany()
->sum('base_amount')
);
array_push(
$receipt_totals,
Payment::whereBetween(
'payment_date',
[$start->format('Y-m-d'), $end->format('Y-m-d')]
)
->whereCompany()
->sum('base_amount')
);
array_push(
$net_income_totals,
($receipt_totals[$i] - $expense_totals[$i])
);
$i++;
array_push($months, $start->translatedFormat('M'));
$monthCounter++;
$end->startOfMonth();
$start->addMonth()->startOfMonth();
$end->addMonth()->endOfMonth();
}
$start->subMonth()->endOfMonth();
$total_sales = Invoice::whereBetween(
'invoice_date',
[$startDate->format('Y-m-d'), $start->format('Y-m-d')]
)
->whereCompany()
->sum('base_total');
$total_receipts = Payment::whereBetween(
'payment_date',
[$startDate->format('Y-m-d'), $start->format('Y-m-d')]
)
->whereCompany()
->sum('base_amount');
$total_expenses = Expense::whereBetween(
'expense_date',
[$startDate->format('Y-m-d'), $start->format('Y-m-d')]
)
->whereCompany()
->sum('base_amount');
$total_net_income = (int) $total_receipts - (int) $total_expenses;
$chart_data = [
'months' => $months,
'invoice_totals' => $invoice_totals,
'expense_totals' => $expense_totals,
'receipt_totals' => $receipt_totals,
'net_income_totals' => $net_income_totals,
];
$total_customer_count = Customer::whereCompany()->count();
$total_invoice_count = Invoice::whereCompany()
->count();
$total_estimate_count = Estimate::whereCompany()->count();
$total_amount_due = Invoice::whereCompany()
->sum('base_due_amount');
$recent_due_invoices = Invoice::with('customer')
->whereCompany()
->where('base_due_amount', '>', 0)
->take(5)
->latest()
->get();
$recent_estimates = Estimate::with('customer')->whereCompany()->take(5)->latest()->get();
return response()->json([
'total_amount_due' => $total_amount_due,
'total_customer_count' => $total_customer_count,
'total_invoice_count' => $total_invoice_count,
'total_estimate_count' => $total_estimate_count,
'recent_due_invoices' => BouncerFacade::can('view-invoice', Invoice::class) ? $recent_due_invoices : [],
'recent_estimates' => BouncerFacade::can('view-estimate', Estimate::class) ? $recent_estimates : [],
'chart_data' => $chart_data,
'total_sales' => $total_sales,
'total_receipts' => $total_receipts,
'total_expenses' => $total_expenses,
'total_net_income' => $total_net_income,
]);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers\Company\Estimate;
use App\Http\Controllers\Controller;
use App\Models\Estimate;
use App\Services\Pdf\PdfTemplateUtils;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class EstimateTemplatesController extends Controller
{
/**
* Handle the incoming request.
*
* @return JsonResponse
*/
public function __invoke(Request $request)
{
$this->authorize('viewAny', Estimate::class);
$estimateTemplates = PdfTemplateUtils::getFormattedTemplates('estimate');
return response()->json([
'estimateTemplates' => $estimateTemplates,
]);
}
}

View File

@@ -0,0 +1,141 @@
<?php
namespace App\Http\Controllers\Company\Estimate;
use App\Http\Controllers\Controller;
use App\Http\Requests\DeleteEstimatesRequest;
use App\Http\Requests\EstimatesRequest;
use App\Http\Requests\SendEstimatesRequest;
use App\Http\Resources\EstimateResource;
use App\Http\Resources\InvoiceResource;
use App\Jobs\GenerateEstimatePdfJob;
use App\Models\Estimate;
use App\Models\Invoice;
use App\Services\EstimateService;
use Illuminate\Http\Request;
use Illuminate\Mail\Markdown;
class EstimatesController extends Controller
{
public function __construct(
private readonly EstimateService $estimateService,
) {}
public function index(Request $request)
{
$this->authorize('viewAny', Estimate::class);
$limit = $request->has('limit') ? $request->limit : 10;
$estimates = Estimate::whereCompany()
->join('customers', 'customers.id', '=', 'estimates.customer_id')
->applyFilters($request->all())
->select('estimates.*', 'customers.name')
->latest()
->paginateData($limit);
return EstimateResource::collection($estimates)
->additional(['meta' => [
'estimate_total_count' => Estimate::whereCompany()->count(),
]]);
}
public function store(EstimatesRequest $request)
{
$this->authorize('create', Estimate::class);
$estimate = $this->estimateService->create($request);
if ($request->has('estimateSend')) {
$this->estimateService->send($estimate, $request->only(['title', 'body']));
}
GenerateEstimatePdfJob::dispatch($estimate);
return new EstimateResource($estimate);
}
public function show(Request $request, Estimate $estimate)
{
$this->authorize('view', $estimate);
return new EstimateResource($estimate);
}
public function update(EstimatesRequest $request, Estimate $estimate)
{
$this->authorize('update', $estimate);
$estimate = $this->estimateService->update($estimate, $request);
GenerateEstimatePdfJob::dispatch($estimate, true);
return new EstimateResource($estimate);
}
public function delete(DeleteEstimatesRequest $request)
{
$this->authorize('delete multiple estimates');
$ids = Estimate::whereCompany()
->whereIn('id', $request->ids)
->pluck('id');
Estimate::destroy($ids);
return response()->json([
'success' => true,
]);
}
public function send(SendEstimatesRequest $request, Estimate $estimate)
{
$this->authorize('send estimate', $estimate);
$response = $this->estimateService->send($estimate, $request->all());
return response()->json($response);
}
public function sendPreview(SendEstimatesRequest $request, Estimate $estimate)
{
$this->authorize('send estimate', $estimate);
$markdown = new Markdown(view(), config('mail.markdown'));
$data = $this->estimateService->sendEstimateData($estimate, $request->all());
$data['url'] = $estimate->estimatePdfUrl;
return $markdown->render('emails.send.estimate', ['data' => $data]);
}
public function clone(Request $request, Estimate $estimate)
{
$this->authorize('view', $estimate);
$this->authorize('create', Estimate::class);
$newEstimate = $this->estimateService->clone($estimate);
return new EstimateResource($newEstimate);
}
public function convertToInvoice(Request $request, Estimate $estimate)
{
$this->authorize('create', Invoice::class);
$invoice = $this->estimateService->convertToInvoice($estimate);
return new InvoiceResource($invoice);
}
public function changeStatus(Request $request, Estimate $estimate)
{
$this->authorize('send estimate', $estimate);
$this->estimateService->changeStatus($estimate, $request->status);
return response()->json([
'success' => true,
]);
}
}

View File

@@ -0,0 +1,347 @@
<?php
namespace App\Http\Controllers\Company\ExchangeRate;
use App\Http\Controllers\Controller;
use App\Http\Requests\BulkExchangeRateRequest;
use App\Http\Requests\ExchangeRateProviderRequest;
use App\Http\Resources\ExchangeRateProviderResource;
use App\Models\CompanySetting;
use App\Models\Currency;
use App\Models\Estimate;
use App\Models\ExchangeRateLog;
use App\Models\ExchangeRateProvider;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\Tax;
use App\Traits\ExchangeRateProvidersTrait;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Arr;
class ExchangeRateProviderController extends Controller
{
use ExchangeRateProvidersTrait;
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index(Request $request)
{
$this->authorize('viewAny', ExchangeRateProvider::class);
$limit = $request->has('limit') ? $request->limit : 5;
$exchangeRateProviders = ExchangeRateProvider::whereCompany()->paginate($limit);
return ExchangeRateProviderResource::collection($exchangeRateProviders);
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return Response
*/
public function store(ExchangeRateProviderRequest $request)
{
$this->authorize('create', ExchangeRateProvider::class);
$query = ExchangeRateProvider::checkActiveCurrencies($request);
if (count($query) !== 0) {
return respondJson('currency_used', 'Currency used.');
}
$checkConverterApi = ExchangeRateProvider::checkExchangeRateProviderStatus($request);
if ($checkConverterApi->status() == 200) {
$exchangeRateProvider = ExchangeRateProvider::createFromRequest($request);
return new ExchangeRateProviderResource($exchangeRateProvider);
}
return $checkConverterApi;
}
/**
* Display the specified resource.
*
* @return Response
*/
public function show(ExchangeRateProvider $exchangeRateProvider)
{
$this->authorize('view', $exchangeRateProvider);
return new ExchangeRateProviderResource($exchangeRateProvider);
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @return Response
*/
public function update(ExchangeRateProviderRequest $request, ExchangeRateProvider $exchangeRateProvider)
{
$this->authorize('update', $exchangeRateProvider);
$query = $exchangeRateProvider->checkUpdateActiveCurrencies($request);
if (count($query) !== 0) {
return respondJson('currency_used', 'Currency used.');
}
$checkConverterApi = ExchangeRateProvider::checkExchangeRateProviderStatus($request);
if ($checkConverterApi->status() == 200) {
$exchangeRateProvider->updateFromRequest($request);
return new ExchangeRateProviderResource($exchangeRateProvider);
}
return $checkConverterApi;
}
/**
* Remove the specified resource from storage.
*
* @return Response
*/
public function destroy(ExchangeRateProvider $exchangeRateProvider)
{
$this->authorize('delete', $exchangeRateProvider);
if ($exchangeRateProvider->active == true) {
return respondJson('provider_active', 'Provider Active.');
}
$exchangeRateProvider->delete();
return response()->json([
'success' => true,
]);
}
public function activeProvider(Request $request, Currency $currency)
{
$query = ExchangeRateProvider::whereCompany()->whereJsonContains('currencies', $currency->code)
->where('active', true)
->get();
if (count($query) !== 0) {
return response()->json([
'success' => true,
'message' => 'provider_active',
], 200);
}
return response()->json([
'error' => 'no_active_provider',
], 200);
}
public function getRate(Request $request, Currency $currency)
{
$settings = CompanySetting::getSettings(['currency'], $request->header('company'));
$baseCurrency = Currency::findOrFail($settings['currency']);
$query = ExchangeRateProvider::whereJsonContains('currencies', $currency->code)
->where('active', true)
->get()
->toArray();
$exchange_rate = ExchangeRateLog::where('base_currency_id', $currency->id)
->where('currency_id', $baseCurrency->id)
->orderBy('created_at', 'desc')
->value('exchange_rate');
if ($query) {
$filter = Arr::only($query[0], ['key', 'driver', 'driver_config']);
$exchange_rate_value = $this->getExchangeRate($filter, $currency->code, $baseCurrency->code);
if ($exchange_rate_value->status() == 200) {
return $exchange_rate_value;
}
}
if ($exchange_rate) {
return response()->json([
'exchangeRate' => [$exchange_rate],
], 200);
}
return response()->json([
'error' => 'no_exchange_rate_available',
], 200);
}
public function supportedCurrencies(Request $request)
{
$this->authorize('viewAny', ExchangeRateProvider::class);
return $this->getSupportedCurrencies($request);
}
public function usedCurrencies(Request $request)
{
$this->authorize('viewAny', ExchangeRateProvider::class);
$providerId = $request->provider_id;
$activeExchangeRateProviders = ExchangeRateProvider::where('active', true)
->whereCompany()
->when($providerId, function ($query) use ($providerId) {
return $query->where('id', '<>', $providerId);
})
->pluck('currencies');
$activeExchangeRateProvider = [];
foreach ($activeExchangeRateProviders as $data) {
if (is_array($data)) {
for ($limit = 0; $limit < count($data); $limit++) {
$activeExchangeRateProvider[] = $data[$limit];
}
}
}
$allExchangeRateProviders = ExchangeRateProvider::whereCompany()->pluck('currencies');
$allExchangeRateProvider = [];
foreach ($allExchangeRateProviders as $data) {
if (is_array($data)) {
for ($limit = 0; $limit < count($data); $limit++) {
$allExchangeRateProvider[] = $data[$limit];
}
}
}
return response()->json([
'allUsedCurrencies' => $allExchangeRateProvider ? $allExchangeRateProvider : [],
'activeUsedCurrencies' => $activeExchangeRateProvider ? $activeExchangeRateProvider : [],
]);
}
public function usedCurrenciesWithoutRate(Request $request)
{
$invoices = Invoice::where('exchange_rate', null)->pluck('currency_id')->toArray();
$taxes = Tax::where('exchange_rate', null)->pluck('currency_id')->toArray();
$estimates = Estimate::where('exchange_rate', null)->pluck('currency_id')->toArray();
$payments = Payment::where('exchange_rate', null)->pluck('currency_id')->toArray();
$currencies = array_merge($invoices, $taxes, $estimates, $payments);
return response()->json([
'currencies' => Currency::whereIn('id', $currencies)->get(),
]);
}
public function bulkUpdate(BulkExchangeRateRequest $request)
{
$bulkExchangeRate = CompanySetting::getSetting('bulk_exchange_rate_configured', $request->header('company'));
if ($bulkExchangeRate == 'NO') {
if ($request->currencies) {
foreach ($request->currencies as $currency) {
$currency['exchange_rate'] = $currency['exchange_rate'] ?? 1;
$invoices = Invoice::where('currency_id', $currency['id'])->get();
if ($invoices) {
foreach ($invoices as $invoice) {
$invoice->update([
'exchange_rate' => $currency['exchange_rate'],
'base_discount_val' => $invoice->sub_total * $currency['exchange_rate'],
'base_sub_total' => $invoice->sub_total * $currency['exchange_rate'],
'base_total' => $invoice->total * $currency['exchange_rate'],
'base_tax' => $invoice->tax * $currency['exchange_rate'],
'base_due_amount' => $invoice->due_amount * $currency['exchange_rate'],
]);
$this->updateItemsExchangeRate($invoice);
}
}
$estimates = Estimate::where('currency_id', $currency['id'])->get();
if ($estimates) {
foreach ($estimates as $estimate) {
$estimate->update([
'exchange_rate' => $currency['exchange_rate'],
'base_discount_val' => $estimate->sub_total * $currency['exchange_rate'],
'base_sub_total' => $estimate->sub_total * $currency['exchange_rate'],
'base_total' => $estimate->total * $currency['exchange_rate'],
'base_tax' => $estimate->tax * $currency['exchange_rate'],
]);
$this->updateItemsExchangeRate($estimate);
}
}
$taxes = Tax::where('currency_id', $currency['id'])->get();
if ($taxes) {
foreach ($taxes as $tax) {
$tax->base_amount = $tax->base_amount * $currency['exchange_rate'];
$tax->save();
}
}
$payments = Payment::where('currency_id', $currency['id'])->get();
if ($payments) {
foreach ($payments as $payment) {
$payment->exchange_rate = $currency['exchange_rate'];
$payment->base_amount = $payment->amount * $currency['exchange_rate'];
$payment->save();
}
}
}
}
$settings = [
'bulk_exchange_rate_configured' => 'YES',
];
CompanySetting::setSettings($settings, $request->header('company'));
return response()->json([
'success' => true,
]);
}
return response()->json([
'error' => false,
]);
}
private function updateItemsExchangeRate($model): void
{
foreach ($model->items as $item) {
$item->update([
'exchange_rate' => $model->exchange_rate,
'base_discount_val' => $item->discount_val * $model->exchange_rate,
'base_price' => $item->price * $model->exchange_rate,
'base_tax' => $item->tax * $model->exchange_rate,
'base_total' => $item->total * $model->exchange_rate,
]);
$this->updateTaxesExchangeRate($item);
}
$this->updateTaxesExchangeRate($model);
}
private function updateTaxesExchangeRate($model): void
{
if ($model->taxes()->exists()) {
$model->taxes->map(function ($tax) use ($model) {
$tax->update([
'exchange_rate' => $model->exchange_rate,
'base_amount' => $tax->amount * $model->exchange_rate,
]);
});
}
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace App\Http\Controllers\Company\Expense;
use App\ExpensesCategory;
use App\Http\Controllers\Controller;
use App\Http\Requests\ExpenseCategoryRequest;
use App\Http\Resources\ExpenseCategoryResource;
use App\Models\ExpenseCategory;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class ExpenseCategoriesController extends Controller
{
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index(Request $request)
{
$this->authorize('viewAny', ExpenseCategory::class);
$limit = $request->has('limit') ? $request->limit : 5;
$categories = ExpenseCategory::applyFilters($request->all())
->whereCompany()
->latest()
->paginateData($limit);
return ExpenseCategoryResource::collection($categories);
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return Response
*/
public function store(ExpenseCategoryRequest $request)
{
$this->authorize('create', ExpenseCategory::class);
$category = ExpenseCategory::create($request->getExpenseCategoryPayload());
return new ExpenseCategoryResource($category);
}
/**
* Display the specified resource.
*
* @return Response
*/
public function show(ExpenseCategory $category)
{
$this->authorize('view', $category);
return new ExpenseCategoryResource($category);
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @param ExpenseCategory $ExpenseCategory
* @return Response
*/
public function update(ExpenseCategoryRequest $request, ExpenseCategory $category)
{
$this->authorize('update', $category);
$category->update($request->getExpenseCategoryPayload());
return new ExpenseCategoryResource($category);
}
/**
* Remove the specified resource from storage.
*
* @param ExpensesCategory $category
* @return Response
*/
public function destroy(ExpenseCategory $category)
{
$this->authorize('delete', $category);
if ($category->expenses() && $category->expenses()->count() > 0) {
return respondJson('expense_attached', 'Expense Attached');
}
$category->delete();
return response()->json([
'success' => true,
]);
}
}

View File

@@ -0,0 +1,136 @@
<?php
namespace App\Http\Controllers\Company\Expense;
use App\Http\Controllers\Controller;
use App\Http\Requests\DeleteExpensesRequest;
use App\Http\Requests\ExpenseRequest;
use App\Http\Requests\UploadExpenseReceiptRequest;
use App\Http\Resources\ExpenseResource;
use App\Models\Expense;
use App\Services\ExpenseService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ExpensesController extends Controller
{
public function __construct(
private readonly ExpenseService $expenseService,
) {}
/**
* Display a listing of the resource.
*
* @return JsonResponse
*/
public function index(Request $request)
{
$this->authorize('viewAny', Expense::class);
$limit = $request->has('limit') ? $request->limit : 10;
$expenses = Expense::with('category', 'creator', 'fields')
->whereCompany()
->leftJoin('customers', 'customers.id', '=', 'expenses.customer_id')
->join('expense_categories', 'expense_categories.id', '=', 'expenses.expense_category_id')
->applyFilters($request->all())
->select('expenses.*', 'expense_categories.name', 'customers.name as user_name')
->paginateData($limit);
return ExpenseResource::collection($expenses)
->additional(['meta' => [
'expense_total_count' => Expense::whereCompany()->count(),
]]);
}
/**
* Store a newly created resource in storage.
*
* @return JsonResponse
*/
public function store(ExpenseRequest $request)
{
$this->authorize('create', Expense::class);
$expense = $this->expenseService->create($request);
return new ExpenseResource($expense);
}
/**
* Display the specified resource.
*
* @return JsonResponse
*/
public function show(Expense $expense)
{
$this->authorize('view', $expense);
return new ExpenseResource($expense);
}
/**
* Update the specified resource in storage.
*
* @return JsonResponse
*/
public function update(ExpenseRequest $request, Expense $expense)
{
$this->authorize('update', $expense);
$this->expenseService->update($expense, $request);
return new ExpenseResource($expense);
}
public function delete(DeleteExpensesRequest $request)
{
$this->authorize('delete multiple expenses');
$ids = Expense::whereCompany()
->whereIn('id', $request->ids)
->pluck('id');
Expense::destroy($ids);
return response()->json([
'success' => true,
]);
}
public function showReceipt(Expense $expense)
{
$this->authorize('view', $expense);
if ($expense) {
$media = $expense->getFirstMedia('receipts');
if ($media) {
return response()->file($media->getPath());
}
return respondJson('receipt_does_not_exist', 'Receipt does not exist.');
}
}
public function uploadReceipt(UploadExpenseReceiptRequest $request, Expense $expense)
{
$this->authorize('update', $expense);
$data = json_decode($request->attachment_receipt);
if ($data) {
if ($request->type === 'edit') {
$expense->clearMediaCollection('receipts');
}
$expense->addMediaFromBase64($data->data)
->usingFileName($data->name)
->toMediaCollection('receipts');
}
return response()->json([
'success' => 'Expense receipts uploaded successfully',
], 200);
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace App\Http\Controllers\Company\General;
use App\Http\Controllers\Controller;
use App\Http\Resources\CompanyResource;
use App\Http\Resources\UserResource;
use App\Models\Company;
use App\Models\CompanySetting;
use App\Models\Currency;
use App\Models\Module;
use App\Models\Setting;
use App\Traits\GeneratesMenuTrait;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Silber\Bouncer\BouncerFacade;
class BootstrapController extends Controller
{
use GeneratesMenuTrait;
/**
* Handle the incoming request.
*
* @return JsonResponse
*/
public function __invoke(Request $request)
{
$current_user = $request->user();
$current_user_settings = $current_user->getAllSettings();
$main_menu = $this->generateMenu('main_menu', $current_user);
$setting_menu = $this->generateMenu('setting_menu', $current_user);
$companies = $current_user->companies;
$current_company = Company::find($request->header('company'));
if ((! $current_company) || ($current_company && ! $current_user->hasCompany($current_company->id))) {
$current_company = $current_user->companies()->first();
}
$current_company_settings = CompanySetting::getAllSettings($current_company->id);
$current_company_currency = $current_company_settings->has('currency')
? Currency::find($current_company_settings->get('currency'))
: Currency::first();
BouncerFacade::refreshFor($current_user);
$global_settings = Setting::getSettings([
'api_token',
'admin_portal_theme',
'admin_portal_logo',
'login_page_logo',
'login_page_heading',
'login_page_description',
'admin_page_title',
'copyright_text',
'save_pdf_to_disk',
]);
return response()->json([
'current_user' => new UserResource($current_user),
'current_user_settings' => $current_user_settings,
'current_user_abilities' => $current_user->getAbilities(),
'companies' => CompanyResource::collection($companies),
'current_company' => new CompanyResource($current_company),
'current_company_settings' => $current_company_settings,
'current_company_currency' => $current_company_currency,
'config' => config('invoiceshelf'),
'global_settings' => $global_settings,
'main_menu' => $main_menu,
'setting_menu' => $setting_menu,
'modules' => Module::where('enabled', true)->pluck('name'),
]);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\Company\General;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class ConfigController extends Controller
{
/**
* Handle the incoming request.
*
* @return Response
*/
public function __invoke(Request $request)
{
return response()->json([
$request->key => config('invoiceshelf.'.$request->key),
]);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Http\Controllers\Company\General;
use App\Http\Controllers\Controller;
use App\Support\Formatters\DateFormatter;
use App\Support\Formatters\TimeFormatter;
use App\Support\Formatters\TimeZones;
use Illuminate\Http\JsonResponse;
class FormatsController extends Controller
{
public function dateFormats(): JsonResponse
{
return response()->json([
'date_formats' => DateFormatter::get_list(),
]);
}
public function timeFormats(): JsonResponse
{
return response()->json([
'time_formats' => TimeFormatter::get_list(),
]);
}
public function timezones(): JsonResponse
{
return response()->json([
'time_zones' => TimeZones::get_list(),
]);
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace App\Http\Controllers\Company\General;
use App\Http\Controllers\Controller;
use App\Http\Requests\NotesRequest;
use App\Http\Resources\NoteResource;
use App\Models\Note;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class NotesController extends Controller
{
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index(Request $request)
{
$this->authorize('view notes');
$limit = $request->limit ?? 10;
$notes = Note::latest()
->whereCompany()
->applyFilters($request->all())
->paginate($limit);
return NoteResource::collection($notes);
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return Response
*/
public function store(NotesRequest $request)
{
$this->authorize('manage notes');
$note = Note::create($request->getNotesPayload());
if ($note->is_default) {
Note::where('id', '!=', $note->id)
->where('type', $note->type)
->where('is_default', true)
->update([
'is_default' => false,
]);
}
return new NoteResource($note);
}
/**
* Display the specified resource.
*
* @return Response
*/
public function show(Note $note)
{
$this->authorize('view notes');
return new NoteResource($note);
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @return Response
*/
public function update(NotesRequest $request, Note $note)
{
$this->authorize('manage notes');
$note->update($request->getNotesPayload());
if ($note->is_default) {
Note::where('id', '!=', $note->id)
->where('type', $note->type)
->where('is_default', true)
->update([
'is_default' => false,
]);
}
return new NoteResource($note);
}
/**
* Remove the specified resource from storage.
*
* @return Response
*/
public function destroy(Note $note)
{
$this->authorize('manage notes');
$note->delete();
return response()->json([
'success' => true,
]);
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers\Company\General;
use App\Http\Controllers\Controller;
use App\Models\Customer;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class SearchController extends Controller
{
/**
* Handle the incoming request.
*
* @return Response
*/
public function __invoke(Request $request)
{
$user = $request->user();
$customers = Customer::applyFilters($request->only(['search']))
->whereCompany()
->latest()
->paginate(10);
if ($user->isOwner()) {
$users = User::whereCompany()
->applyFilters($request->only(['search']))
->latest()
->paginate(10);
}
return response()->json([
'customers' => $customers,
'users' => $users ?? [],
]);
}
public function users(Request $request)
{
$this->authorize('create', User::class);
$users = User::whereEmail($request->email)
->latest()
->paginate(10);
return response()->json(['users' => $users]);
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace App\Http\Controllers\Company\General;
use App\Http\Controllers\Controller;
use App\Models\Estimate;
use App\Models\Invoice;
use App\Models\Payment;
use App\Services\SerialNumberService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class SerialNumberController extends Controller
{
public function nextNumber(Request $request, Invoice $invoice, Estimate $estimate, Payment $payment): JsonResponse
{
$key = $request->key;
$nextNumber = null;
$serial = (new SerialNumberService)
->setCompany($request->header('company'))
->setCustomer($request->userId);
try {
switch ($key) {
case 'invoice':
$nextNumber = $serial->setModel($invoice)
->setModelObject($request->model_id)
->getNextNumber();
break;
case 'estimate':
$nextNumber = $serial->setModel($estimate)
->setModelObject($request->model_id)
->getNextNumber();
break;
case 'payment':
$nextNumber = $serial->setModel($payment)
->setModelObject($request->model_id)
->getNextNumber();
break;
default:
return response()->json([
'success' => false,
]);
}
} catch (\Exception $exception) {
return response()->json([
'success' => false,
'message' => $exception->getMessage(),
]);
}
return response()->json([
'success' => true,
'nextNumber' => $nextNumber,
]);
}
public function placeholders(Request $request): JsonResponse
{
if ($request->format) {
$placeholders = SerialNumberService::getPlaceholders($request->format);
} else {
$placeholders = [];
}
return response()->json([
'success' => true,
'placeholders' => $placeholders,
]);
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers\Company\Invoice;
use App\Http\Controllers\Controller;
use App\Models\Invoice;
use App\Services\Pdf\PdfTemplateUtils;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class InvoiceTemplatesController extends Controller
{
/**
* Handle the incoming request.
*
*
* @return JsonResponse
*
* @throws AuthorizationException
*/
public function __invoke(Request $request)
{
$this->authorize('viewAny', Invoice::class);
$invoiceTemplates = PdfTemplateUtils::getFormattedTemplates('invoice');
return response()->json([
'invoiceTemplates' => $invoiceTemplates,
]);
}
}

View File

@@ -0,0 +1,160 @@
<?php
namespace App\Http\Controllers\Company\Invoice;
use App\Http\Controllers\Controller;
use App\Http\Requests;
use App\Http\Requests\DeleteInvoiceRequest;
use App\Http\Requests\SendInvoiceRequest;
use App\Http\Resources\InvoiceResource;
use App\Jobs\GenerateInvoicePdfJob;
use App\Models\Invoice;
use App\Services\InvoiceService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Mail\Markdown;
class InvoicesController extends Controller
{
public function __construct(
private readonly InvoiceService $invoiceService,
) {}
/**
* Display a listing of the resource.
*
* @return JsonResponse
*/
public function index(Request $request)
{
$this->authorize('viewAny', Invoice::class);
$limit = $request->input('limit', 10);
$invoices = Invoice::whereCompany()
->applyFilters($request->all())
->with('customer')
->latest()
->paginateData($limit);
return InvoiceResource::collection($invoices)
->additional(['meta' => [
'invoice_total_count' => Invoice::whereCompany()->count(),
]]);
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return JsonResponse
*/
public function store(Requests\InvoicesRequest $request)
{
$this->authorize('create', Invoice::class);
$invoice = $this->invoiceService->create($request);
if ($request->has('invoiceSend')) {
$this->invoiceService->send($invoice, $request->only(['subject', 'body']));
}
GenerateInvoicePdfJob::dispatch($invoice);
return new InvoiceResource($invoice);
}
/**
* Display the specified resource.
*
* @return JsonResponse
*/
public function show(Request $request, Invoice $invoice)
{
$this->authorize('view', $invoice);
return new InvoiceResource($invoice);
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @return JsonResponse
*/
public function update(Requests\InvoicesRequest $request, Invoice $invoice)
{
$this->authorize('update', $invoice);
$invoice = $this->invoiceService->update($invoice, $request);
GenerateInvoicePdfJob::dispatch($invoice, true);
return new InvoiceResource($invoice);
}
/**
* delete the specified resources in storage.
*
* @param Request $request
* @return JsonResponse
*/
public function delete(DeleteInvoiceRequest $request)
{
$this->authorize('delete multiple invoices');
$ids = Invoice::whereCompany()
->whereIn('id', $request->ids)
->pluck('id');
$this->invoiceService->delete($ids);
return response()->json([
'success' => true,
]);
}
public function send(SendInvoiceRequest $request, Invoice $invoice)
{
$this->authorize('send invoice', $invoice);
$this->invoiceService->send($invoice, $request->all());
return response()->json([
'success' => true,
]);
}
public function sendPreview(SendInvoiceRequest $request, Invoice $invoice)
{
$this->authorize('send invoice', $invoice);
$markdown = new Markdown(view(), config('mail.markdown'));
$data = $this->invoiceService->sendInvoiceData($invoice, $request->all());
$data['url'] = $invoice->invoicePdfUrl;
return $markdown->render('emails.send.invoice', ['data' => $data]);
}
public function clone(Request $request, Invoice $invoice)
{
$this->authorize('view', $invoice);
$this->authorize('create', Invoice::class);
$newInvoice = $this->invoiceService->clone($invoice);
return new InvoiceResource($newInvoice);
}
public function changeStatus(Request $request, Invoice $invoice)
{
$this->authorize('send invoice', $invoice);
$this->invoiceService->changeStatus($invoice, $request->status);
return response()->json([
'success' => true,
]);
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace App\Http\Controllers\Company\Item;
use App\Http\Controllers\Controller;
use App\Http\Requests;
use App\Http\Requests\DeleteItemsRequest;
use App\Http\Resources\ItemResource;
use App\Models\Item;
use App\Models\TaxType;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class ItemsController extends Controller
{
/**
* Retrieve a list of existing Items.
*
* @return JsonResponse
*/
public function index(Request $request)
{
$this->authorize('viewAny', Item::class);
$limit = $request->has('limit') ? $request->limit : 10;
$items = Item::whereCompany()
->leftJoin('units', 'units.id', '=', 'items.unit_id')
->applyFilters($request->all())
->select('items.*', 'units.name as unit_name')
->latest()
->paginateData($limit);
return ItemResource::collection($items)
->additional(['meta' => [
'tax_types' => TaxType::whereCompany()->latest()->get(),
'item_total_count' => Item::whereCompany()->count(),
]]);
}
/**
* Create Item.
*
* @param App\Http\Requests\ItemsRequest $request
* @return JsonResponse
*/
public function store(Requests\ItemsRequest $request)
{
$this->authorize('create', Item::class);
$item = Item::createItem($request);
return new ItemResource($item);
}
/**
* get an existing Item.
*
* @return JsonResponse
*/
public function show(Item $item)
{
$this->authorize('view', $item);
return new ItemResource($item);
}
/**
* Update an existing Item.
*
* @param App\Http\Requests\ItemsRequest $request
* @return JsonResponse
*/
public function update(Requests\ItemsRequest $request, Item $item)
{
$this->authorize('update', $item);
$item = $item->updateItem($request);
return new ItemResource($item);
}
/**
* Delete a list of existing Items.
*
* @param Request $request
* @return JsonResponse
*/
public function delete(DeleteItemsRequest $request)
{
$this->authorize('delete multiple items');
$ids = Item::whereCompany()
->whereIn('id', $request->ids)
->pluck('id');
Item::destroy($ids);
return response()->json([
'success' => true,
]);
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace App\Http\Controllers\Company\Item;
use App\Http\Controllers\Controller;
use App\Http\Requests\UnitRequest;
use App\Http\Resources\UnitResource;
use App\Models\Unit;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class UnitsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index(Request $request)
{
$this->authorize('viewAny', Unit::class);
$limit = $request->has('limit') ? $request->limit : 5;
$units = Unit::applyFilters($request->all())
->whereCompany()
->latest()
->paginateData($limit);
return UnitResource::collection($units);
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return Response
*/
public function store(UnitRequest $request)
{
$this->authorize('create', Unit::class);
$unit = Unit::create($request->getUnitPayload());
return new UnitResource($unit);
}
/**
* Display the specified resource.
*
* @return Response
*/
public function show(Unit $unit)
{
$this->authorize('view', $unit);
return new UnitResource($unit);
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @return Response
*/
public function update(UnitRequest $request, Unit $unit)
{
$this->authorize('update', $unit);
$unit->update($request->getUnitPayload());
return new UnitResource($unit);
}
/**
* Remove the specified resource from storage.
*
* @return Response
*/
public function destroy(Unit $unit)
{
$this->authorize('delete', $unit);
if ($unit->items()->exists()) {
return respondJson('items_attached', 'Items Attached');
}
$unit->delete();
return response()->json([
'success' => 'Unit deleted successfully',
]);
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers\Company\Mobile;
use App\Http\Controllers\Controller;
use App\Http\Requests\LoginRequest;
use App\Models\User;
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::where('email', $request->username)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
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();
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace App\Http\Controllers\Company\Payment;
use App\Http\Controllers\Controller;
use App\Http\Requests\PaymentMethodRequest;
use App\Http\Resources\PaymentMethodResource;
use App\Models\PaymentMethod;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class PaymentMethodsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index(Request $request)
{
$this->authorize('viewAny', PaymentMethod::class);
$limit = $request->has('limit') ? $request->limit : 5;
$paymentMethods = PaymentMethod::applyFilters($request->all())
->where('type', PaymentMethod::TYPE_GENERAL)
->whereCompany()
->latest()
->paginateData($limit);
return PaymentMethodResource::collection($paymentMethods);
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return Response
*/
public function store(PaymentMethodRequest $request)
{
$this->authorize('create', PaymentMethod::class);
$paymentMethod = PaymentMethod::createPaymentMethod($request);
return new PaymentMethodResource($paymentMethod);
}
/**
* Display the specified resource.
*
* @return Response
*/
public function show(PaymentMethod $paymentMethod)
{
$this->authorize('view', $paymentMethod);
return new PaymentMethodResource($paymentMethod);
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @return Response
*/
public function update(PaymentMethodRequest $request, PaymentMethod $paymentMethod)
{
$this->authorize('update', $paymentMethod);
$paymentMethod->update($request->getPaymentMethodPayload());
return new PaymentMethodResource($paymentMethod);
}
/**
* Remove the specified resource from storage.
*
* @return Response
*/
public function destroy(PaymentMethod $paymentMethod)
{
$this->authorize('delete', $paymentMethod);
if ($paymentMethod->payments()->exists()) {
return respondJson('payments_attached', 'Payments Attached.');
}
if ($paymentMethod->expenses()->exists()) {
return respondJson('expenses_attached', 'Expenses Attached.');
}
$paymentMethod->delete();
return response()->json([
'success' => 'Payment method deleted successfully',
]);
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace App\Http\Controllers\Company\Payment;
use App\Http\Controllers\Controller;
use App\Http\Requests\DeletePaymentsRequest;
use App\Http\Requests\PaymentRequest;
use App\Http\Requests\SendPaymentRequest;
use App\Http\Resources\PaymentResource;
use App\Models\Payment;
use App\Services\PaymentService;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Mail\Markdown;
class PaymentsController extends Controller
{
public function __construct(
private readonly PaymentService $paymentService,
) {}
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index(Request $request)
{
$this->authorize('viewAny', Payment::class);
$limit = $request->has('limit') ? $request->limit : 10;
$payments = Payment::whereCompany()
->join('customers', 'customers.id', '=', 'payments.customer_id')
->leftJoin('invoices', 'invoices.id', '=', 'payments.invoice_id')
->leftJoin('payment_methods', 'payment_methods.id', '=', 'payments.payment_method_id')
->applyFilters($request->all())
->select('payments.*', 'customers.name', 'invoices.invoice_number', 'payment_methods.name as payment_mode')
->latest()
->paginateData($limit);
return PaymentResource::collection($payments)
->additional(['meta' => [
'payment_total_count' => Payment::whereCompany()->count(),
]]);
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return Response
*/
public function store(PaymentRequest $request)
{
$this->authorize('create', Payment::class);
$payment = $this->paymentService->create($request);
return new PaymentResource($payment);
}
public function show(Request $request, Payment $payment)
{
$this->authorize('view', $payment);
return new PaymentResource($payment);
}
public function update(PaymentRequest $request, Payment $payment)
{
$this->authorize('update', $payment);
$payment = $this->paymentService->update($payment, $request);
return new PaymentResource($payment);
}
public function delete(DeletePaymentsRequest $request)
{
$this->authorize('delete multiple payments');
$ids = Payment::whereCompany()
->whereIn('id', $request->ids)
->pluck('id');
$this->paymentService->delete($ids);
return response()->json([
'success' => true,
]);
}
public function send(SendPaymentRequest $request, Payment $payment)
{
$this->authorize('send payment', $payment);
$response = $this->paymentService->send($payment, $request->all());
return response()->json($response);
}
public function sendPreview(Request $request, Payment $payment)
{
$this->authorize('send payment', $payment);
$markdown = new Markdown(view(), config('mail.markdown'));
$data = $this->paymentService->sendPaymentData($payment, $request->all());
$data['url'] = $payment->paymentPdfUrl;
return $markdown->render('emails.send.payment', ['data' => $data]);
}
}

View File

@@ -0,0 +1,102 @@
<?php
namespace App\Http\Controllers\Company\RecurringInvoice;
use App\Http\Controllers\Controller;
use App\Http\Requests\RecurringInvoiceRequest;
use App\Http\Resources\RecurringInvoiceResource;
use App\Models\RecurringInvoice;
use App\Services\RecurringInvoiceService;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class RecurringInvoiceController extends Controller
{
public function __construct(
private readonly RecurringInvoiceService $recurringInvoiceService,
) {}
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index(Request $request)
{
$this->authorize('viewAny', RecurringInvoice::class);
$limit = $request->has('limit') ? $request->limit : 10;
$recurringInvoices = RecurringInvoice::whereCompany()
->applyFilters($request->all())
->paginateData($limit);
return RecurringInvoiceResource::collection($recurringInvoices)
->additional(['meta' => [
'recurring_invoice_total_count' => RecurringInvoice::whereCompany()->count(),
]]);
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return Response
*/
public function store(RecurringInvoiceRequest $request)
{
$this->authorize('create', RecurringInvoice::class);
$recurringInvoice = $this->recurringInvoiceService->create($request);
return new RecurringInvoiceResource($recurringInvoice);
}
/**
* Display the specified resource.
*
* @return Response
*/
public function show(RecurringInvoice $recurringInvoice)
{
$this->authorize('view', $recurringInvoice);
return new RecurringInvoiceResource($recurringInvoice);
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @return Response
*/
public function update(RecurringInvoiceRequest $request, RecurringInvoice $recurringInvoice)
{
$this->authorize('update', $recurringInvoice);
$this->recurringInvoiceService->update($recurringInvoice, $request);
return new RecurringInvoiceResource($recurringInvoice);
}
/**
* Remove the specified resource from storage.
*
* @param RecurringInvoice $recurringInvoice
* @return Response
*/
public function delete(Request $request)
{
$this->authorize('delete multiple recurring invoices');
$ids = RecurringInvoice::whereCompany()
->whereIn('id', $request->ids)
->pluck('id');
$this->recurringInvoiceService->delete($ids);
return response()->json([
'success' => true,
]);
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Company\RecurringInvoice;
use App\Http\Controllers\Controller;
use App\Models\RecurringInvoice;
use Illuminate\Http\Request;
class RecurringInvoiceFrequencyController extends Controller
{
public function __invoke(Request $request)
{
$nextInvoiceAt = RecurringInvoice::getNextInvoiceDate($request->frequency, $request->starts_at);
return response()->json([
'success' => true,
'next_invoice_at' => $nextInvoiceAt,
]);
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace App\Http\Controllers\Company\Report;
use App\Facades\Pdf;
use App\Http\Controllers\Controller;
use App\Models\Company;
use App\Models\CompanySetting;
use App\Models\Currency;
use App\Models\Customer;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
class CustomerSalesReportController extends Controller
{
/**
* Handle the incoming request.
*
* @param string $hash
* @return JsonResponse
*/
public function __invoke(Request $request, $hash)
{
$company = Company::where('unique_hash', $hash)->first();
$this->authorize('view report', $company);
$locale = CompanySetting::getSetting('language', $company->id);
App::setLocale($locale);
$start = Carbon::createFromFormat('Y-m-d', $request->from_date);
$end = Carbon::createFromFormat('Y-m-d', $request->to_date);
$customers = Customer::with(['invoices' => function ($query) use ($start, $end) {
$query->whereBetween(
'invoice_date',
[$start->format('Y-m-d'), $end->format('Y-m-d')]
);
}])
->where('company_id', $company->id)
->applyInvoiceFilters($request->only(['from_date', 'to_date']))
->get();
$totalAmount = 0;
foreach ($customers as $customer) {
$customerTotalAmount = 0;
foreach ($customer->invoices as $invoice) {
$customerTotalAmount += $invoice->base_total;
}
$customer->totalAmount = $customerTotalAmount;
$totalAmount += $customerTotalAmount;
}
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->translatedFormat($dateFormat);
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->translatedFormat($dateFormat);
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
$colors = [
'primary_text_color',
'heading_text_color',
'section_heading_text_color',
'border_color',
'body_text_color',
'footer_text_color',
'footer_total_color',
'footer_bg_color',
'date_text_color',
];
$colorSettings = CompanySetting::whereIn('option', $colors)
->whereCompany($company->id)
->get();
view()->share([
'customers' => $customers,
'totalAmount' => $totalAmount,
'colorSettings' => $colorSettings,
'company' => $company,
'from_date' => $from_date,
'to_date' => $to_date,
'currency' => $currency,
]);
$pdf = Pdf::loadView('app.pdf.reports.sales-customers');
if ($request->has('preview')) {
return view('app.pdf.reports.sales-customers');
}
if ($request->has('download')) {
return $pdf->download();
}
return $pdf->stream();
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace App\Http\Controllers\Company\Report;
use App\Facades\Pdf;
use App\Http\Controllers\Controller;
use App\Models\Company;
use App\Models\CompanySetting;
use App\Models\Currency;
use App\Models\Expense;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
class ExpensesReportController extends Controller
{
/**
* Handle the incoming request.
*
* @param string $hash
* @return JsonResponse
*/
public function __invoke(Request $request, $hash)
{
$company = Company::where('unique_hash', $hash)->first();
$this->authorize('view report', $company);
$locale = CompanySetting::getSetting('language', $company->id);
App::setLocale($locale);
$expenseCategories = Expense::with('category')
->whereCompanyId($company->id)
->applyFilters($request->only(['from_date', 'to_date']))
->expensesAttributes()
->get();
$totalAmount = 0;
foreach ($expenseCategories as $category) {
$totalAmount += $category->total_amount;
}
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->translatedFormat($dateFormat);
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->translatedFormat($dateFormat);
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
$colors = [
'primary_text_color',
'heading_text_color',
'section_heading_text_color',
'border_color',
'body_text_color',
'footer_text_color',
'footer_total_color',
'footer_bg_color',
'date_text_color',
];
$colorSettings = CompanySetting::whereIn('option', $colors)
->whereCompany($company->id)
->get();
view()->share([
'expenseCategories' => $expenseCategories,
'colorSettings' => $colorSettings,
'totalExpense' => $totalAmount,
'company' => $company,
'from_date' => $from_date,
'to_date' => $to_date,
'currency' => $currency,
]);
$pdf = Pdf::loadView('app.pdf.reports.expenses');
if ($request->has('preview')) {
return view('app.pdf.reports.expenses');
}
if ($request->has('download')) {
return $pdf->download();
}
return $pdf->stream();
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace App\Http\Controllers\Company\Report;
use App\Facades\Pdf;
use App\Http\Controllers\Controller;
use App\Models\Company;
use App\Models\CompanySetting;
use App\Models\Currency;
use App\Models\InvoiceItem;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
class ItemSalesReportController extends Controller
{
/**
* Handle the incoming request.
*
* @param string $hash
* @return JsonResponse
*/
public function __invoke(Request $request, $hash)
{
$company = Company::where('unique_hash', $hash)->first();
$this->authorize('view report', $company);
$locale = CompanySetting::getSetting('language', $company->id);
App::setLocale($locale);
$items = InvoiceItem::whereCompany($company->id)
->applyInvoiceFilters($request->only(['from_date', 'to_date']))
->itemAttributes()
->get();
$totalAmount = 0;
foreach ($items as $item) {
$totalAmount += $item->total_amount;
}
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->translatedFormat($dateFormat);
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->translatedFormat($dateFormat);
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
$colors = [
'primary_text_color',
'heading_text_color',
'section_heading_text_color',
'border_color',
'body_text_color',
'footer_text_color',
'footer_total_color',
'footer_bg_color',
'date_text_color',
];
$colorSettings = CompanySetting::whereIn('option', $colors)
->whereCompany($company->id)
->get();
view()->share([
'items' => $items,
'colorSettings' => $colorSettings,
'totalAmount' => $totalAmount,
'company' => $company,
'from_date' => $from_date,
'to_date' => $to_date,
'currency' => $currency,
]);
$pdf = Pdf::loadView('app.pdf.reports.sales-items');
if ($request->has('preview')) {
return view('app.pdf.reports.sales-items');
}
if ($request->has('download')) {
return $pdf->download();
}
return $pdf->stream();
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace App\Http\Controllers\Company\Report;
use App\Facades\Pdf;
use App\Http\Controllers\Controller;
use App\Models\Company;
use App\Models\CompanySetting;
use App\Models\Currency;
use App\Models\Expense;
use App\Models\Payment;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
class ProfitLossReportController extends Controller
{
/**
* Handle the incoming request.
*
* @param string $hash
* @return JsonResponse
*/
public function __invoke(Request $request, $hash)
{
$company = Company::where('unique_hash', $hash)->first();
$this->authorize('view report', $company);
$locale = CompanySetting::getSetting('language', $company->id);
App::setLocale($locale);
$paymentsAmount = Payment::whereCompanyId($company->id)
->applyFilters($request->only(['from_date', 'to_date']))
->sum('base_amount');
$expenseCategories = Expense::with('category')
->whereCompanyId($company->id)
->applyFilters($request->only(['from_date', 'to_date']))
->expensesAttributes()
->get();
$totalAmount = 0;
foreach ($expenseCategories as $category) {
$totalAmount += $category->total_amount;
}
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->translatedFormat($dateFormat);
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->translatedFormat($dateFormat);
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
$colors = [
'primary_text_color',
'heading_text_color',
'section_heading_text_color',
'border_color',
'body_text_color',
'footer_text_color',
'footer_total_color',
'footer_bg_color',
'date_text_color',
];
$colorSettings = CompanySetting::whereIn('option', $colors)
->whereCompany($company->id)
->get();
view()->share([
'company' => $company,
'income' => $paymentsAmount,
'expenseCategories' => $expenseCategories,
'totalExpense' => $totalAmount,
'colorSettings' => $colorSettings,
'company' => $company,
'from_date' => $from_date,
'to_date' => $to_date,
'currency' => $currency,
]);
$pdf = Pdf::loadView('app.pdf.reports.profit-loss');
if ($request->has('preview')) {
return view('app.pdf.reports.profit-loss');
}
if ($request->has('download')) {
return $pdf->download();
}
return $pdf->stream();
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace App\Http\Controllers\Company\Report;
use App\Facades\Pdf;
use App\Http\Controllers\Controller;
use App\Models\Company;
use App\Models\CompanySetting;
use App\Models\Currency;
use App\Models\Tax;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
class TaxSummaryReportController extends Controller
{
/**
* Handle the incoming request.
*
* @param string $hash
* @return JsonResponse
*/
public function __invoke(Request $request, $hash)
{
$company = Company::where('unique_hash', $hash)->first();
$this->authorize('view report', $company);
$locale = CompanySetting::getSetting('language', $company->id);
App::setLocale($locale);
$taxTypes = Tax::with('taxType', 'invoice', 'invoiceItem')
->whereCompany($company->id)
->whereInvoicesFilters($request->only(['from_date', 'to_date']))
->taxAttributes()
->get();
$totalAmount = 0;
foreach ($taxTypes as $taxType) {
$totalAmount += $taxType->total_tax_amount;
}
$dateFormat = CompanySetting::getSetting('carbon_date_format', $company->id);
$from_date = Carbon::createFromFormat('Y-m-d', $request->from_date)->translatedFormat($dateFormat);
$to_date = Carbon::createFromFormat('Y-m-d', $request->to_date)->translatedFormat($dateFormat);
$currency = Currency::findOrFail(CompanySetting::getSetting('currency', $company->id));
$colors = [
'primary_text_color',
'heading_text_color',
'section_heading_text_color',
'border_color',
'body_text_color',
'footer_text_color',
'footer_total_color',
'footer_bg_color',
'date_text_color',
];
$colorSettings = CompanySetting::whereIn('option', $colors)
->whereCompany($company->id)
->get();
view()->share([
'taxTypes' => $taxTypes,
'totalTaxAmount' => $totalAmount,
'colorSettings' => $colorSettings,
'company' => $company,
'from_date' => $from_date,
'to_date' => $to_date,
'currency' => $currency,
]);
$pdf = Pdf::loadView('app.pdf.reports.tax-summary');
if ($request->has('preview')) {
return view('app.pdf.reports.tax-summary');
}
if ($request->has('download')) {
return $pdf->download();
}
return $pdf->stream();
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Company\Role;
use App\Http\Controllers\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')]);
}
}

View File

@@ -0,0 +1,120 @@
<?php
namespace App\Http\Controllers\Company\Role;
use App\Http\Controllers\Controller;
use App\Http\Requests\RoleRequest;
use App\Http\Resources\RoleResource;
use App\Models\User;
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;
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Http\Controllers\Company\Settings;
use App\Http\Controllers\Controller;
use App\Http\Requests\CompanyLogoRequest;
use App\Http\Requests\CompanyRequest;
use App\Http\Resources\CompanyResource;
use App\Models\Company;
class CompanyController extends Controller
{
public function updateCompany(CompanyRequest $request)
{
$company = Company::find($request->header('company'));
$this->authorize('manage company', $company);
$company->update($request->getCompanyPayload());
$company->address()->updateOrCreate(['company_id' => $company->id], $request->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) {
$company->clearMediaCollection('logo');
}
if ($data) {
$company = Company::find($request->header('company'));
if ($company) {
$company->clearMediaCollection('logo');
$company->addMediaFromBase64($data->data)
->usingFileName($data->name)
->toMediaCollection('logo');
}
}
return response()->json([
'success' => true,
]);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\Company\Settings;
use App\Http\Controllers\Controller;
use App\Models\Company;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class CompanyCurrencyCheckTransactionsController extends Controller
{
/**
* Handle the incoming request.
*
* @return Response
*/
public function __invoke(Request $request)
{
$company = Company::find($request->header('company'));
$this->authorize('manage company', $company);
return response()->json([
'has_transactions' => $company->hasTransactions(),
]);
}
}

View File

@@ -0,0 +1,155 @@
<?php
namespace App\Http\Controllers\Company\Settings;
use App\Http\Controllers\Controller;
use App\Mail\TestMail;
use App\Models\CompanySetting;
use App\Services\CompanyMailConfigService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Mail;
class CompanyMailConfigurationController extends Controller
{
public function getDefaultConfig(Request $request): JsonResponse
{
$mailConfig = [
'from_name' => config('mail.from.name'),
'from_mail' => config('mail.from.address'),
];
return response()->json($mailConfig);
}
public function getMailConfig(Request $request): JsonResponse
{
$companyId = $request->header('company');
$useCustom = CompanySetting::getSetting('use_custom_mail_config', $companyId) ?? 'NO';
$driver = CompanySetting::getSetting('company_mail_driver', $companyId) ?? '';
$data = [
'use_custom_mail_config' => $useCustom,
'mail_driver' => $driver,
'from_name' => CompanySetting::getSetting('company_from_name', $companyId) ?? '',
'from_mail' => CompanySetting::getSetting('company_from_mail', $companyId) ?? '',
];
switch ($driver) {
case 'smtp':
$data = array_merge($data, [
'mail_host' => CompanySetting::getSetting('company_mail_host', $companyId) ?? '',
'mail_port' => CompanySetting::getSetting('company_mail_port', $companyId) ?? '',
'mail_username' => CompanySetting::getSetting('company_mail_username', $companyId) ?? '',
'mail_password' => CompanySetting::getSetting('company_mail_password', $companyId) ?? '',
'mail_encryption' => CompanySetting::getSetting('company_mail_encryption', $companyId) ?? 'none',
'mail_scheme' => CompanySetting::getSetting('company_mail_scheme', $companyId) ?? '',
'mail_url' => CompanySetting::getSetting('company_mail_url', $companyId) ?? '',
'mail_timeout' => CompanySetting::getSetting('company_mail_timeout', $companyId) ?? '',
'mail_local_domain' => CompanySetting::getSetting('company_mail_local_domain', $companyId) ?? '',
]);
break;
case 'mailgun':
$data = array_merge($data, [
'mail_mailgun_domain' => CompanySetting::getSetting('company_mail_mailgun_domain', $companyId) ?? '',
'mail_mailgun_secret' => CompanySetting::getSetting('company_mail_mailgun_secret', $companyId) ?? '',
'mail_mailgun_endpoint' => CompanySetting::getSetting('company_mail_mailgun_endpoint', $companyId) ?? 'api.mailgun.net',
'mail_mailgun_scheme' => CompanySetting::getSetting('company_mail_mailgun_scheme', $companyId) ?? 'https',
]);
break;
case 'ses':
$data = array_merge($data, [
'mail_ses_key' => CompanySetting::getSetting('company_mail_ses_key', $companyId) ?? '',
'mail_ses_secret' => CompanySetting::getSetting('company_mail_ses_secret', $companyId) ?? '',
'mail_ses_region' => CompanySetting::getSetting('company_mail_ses_region', $companyId) ?? 'us-east-1',
]);
break;
case 'sendmail':
$data = array_merge($data, [
'mail_sendmail_path' => CompanySetting::getSetting('company_mail_sendmail_path', $companyId) ?? '/usr/sbin/sendmail -bs -i',
]);
break;
}
return response()->json($data);
}
public function saveMailConfig(Request $request): JsonResponse
{
$this->authorize('owner only');
$companyId = $request->header('company');
$driver = $request->get('mail_driver', '');
$settings = [
'use_custom_mail_config' => $request->get('use_custom_mail_config', 'NO'),
'company_mail_driver' => $driver,
'company_from_name' => $request->get('from_name', ''),
'company_from_mail' => $request->get('from_mail', ''),
];
switch ($driver) {
case 'smtp':
$settings = array_merge($settings, [
'company_mail_host' => $request->get('mail_host', ''),
'company_mail_port' => $request->get('mail_port', ''),
'company_mail_username' => $request->get('mail_username', ''),
'company_mail_password' => $request->get('mail_password', ''),
'company_mail_encryption' => $request->get('mail_encryption', 'none'),
'company_mail_scheme' => $request->get('mail_scheme', ''),
'company_mail_url' => $request->get('mail_url', ''),
'company_mail_timeout' => $request->get('mail_timeout', ''),
'company_mail_local_domain' => $request->get('mail_local_domain', ''),
]);
break;
case 'mailgun':
$settings = array_merge($settings, [
'company_mail_mailgun_domain' => $request->get('mail_mailgun_domain', ''),
'company_mail_mailgun_secret' => $request->get('mail_mailgun_secret', ''),
'company_mail_mailgun_endpoint' => $request->get('mail_mailgun_endpoint', 'api.mailgun.net'),
'company_mail_mailgun_scheme' => $request->get('mail_mailgun_scheme', 'https'),
]);
break;
case 'ses':
$settings = array_merge($settings, [
'company_mail_ses_key' => $request->get('mail_ses_key', ''),
'company_mail_ses_secret' => $request->get('mail_ses_secret', ''),
'company_mail_ses_region' => $request->get('mail_ses_region', 'us-east-1'),
]);
break;
case 'sendmail':
$settings = array_merge($settings, [
'company_mail_sendmail_path' => $request->get('mail_sendmail_path', '/usr/sbin/sendmail -bs -i'),
]);
break;
}
CompanySetting::setSettings($settings, $companyId);
return response()->json(['success' => true]);
}
public function testMailConfig(Request $request): JsonResponse
{
$this->authorize('owner only');
$this->validate($request, [
'to' => 'required|email',
'subject' => 'required',
'message' => 'required',
]);
CompanyMailConfigService::apply($request->header('company'));
Mail::to($request->to)->send(new TestMail($request->subject, $request->message));
return response()->json(['success' => true]);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Http\Controllers\Company\Settings;
use App\Http\Controllers\Controller;
use App\Http\Requests\GetSettingsRequest;
use App\Http\Requests\UpdateSettingsRequest;
use App\Models\Company;
use App\Models\CompanySetting;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Arr;
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,
]);
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace App\Http\Controllers\Company\Settings;
use App\Http\Controllers\Controller;
use App\Http\Requests\TaxTypeRequest;
use App\Http\Resources\TaxTypeResource;
use App\Models\TaxType;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class TaxTypesController extends Controller
{
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index(Request $request)
{
$this->authorize('viewAny', TaxType::class);
$limit = $request->has('limit') ? $request->limit : 5;
$taxTypes = TaxType::applyFilters($request->all())
->where('type', TaxType::TYPE_GENERAL)
->whereCompany()
->latest()
->paginateData($limit);
return TaxTypeResource::collection($taxTypes);
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return Response
*/
public function store(TaxTypeRequest $request)
{
$this->authorize('create', TaxType::class);
$taxType = TaxType::create($request->getTaxTypePayload());
return new TaxTypeResource($taxType);
}
/**
* Display the specified resource.
*
* @return Response
*/
public function show(TaxType $taxType)
{
$this->authorize('view', $taxType);
return new TaxTypeResource($taxType);
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @return Response
*/
public function update(TaxTypeRequest $request, TaxType $taxType)
{
$this->authorize('update', $taxType);
$taxType->update($request->getTaxTypePayload());
return new TaxTypeResource($taxType);
}
/**
* Remove the specified resource from storage.
*
* @return Response
*/
public function destroy(TaxType $taxType)
{
$this->authorize('delete', $taxType);
if ($taxType->taxes() && $taxType->taxes()->count() > 0) {
return respondJson('taxes_attached', 'Taxes Attached.');
}
$taxType->delete();
return response()->json([
'success' => true,
]);
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Http\Controllers\Company\Settings;
use App\Http\Controllers\Controller;
use App\Http\Requests\AvatarRequest;
use App\Http\Requests\ProfileRequest;
use App\Http\Resources\UserResource;
use Illuminate\Http\Request;
class UserProfileController extends Controller
{
public function show(Request $request)
{
return new UserResource($request->user());
}
public function update(ProfileRequest $request)
{
$user = $request->user();
$user->update($request->validated());
return new UserResource($user);
}
public function uploadAvatar(AvatarRequest $request)
{
$user = auth()->user();
if (isset($request->is_admin_avatar_removed) && (bool) $request->is_admin_avatar_removed) {
$user->clearMediaCollection('admin_avatar');
}
if ($user && $request->hasFile('admin_avatar')) {
$user->clearMediaCollection('admin_avatar');
$user->addMediaFromRequest('admin_avatar')
->toMediaCollection('admin_avatar');
}
if ($user && $request->has('avatar')) {
$data = json_decode($request->avatar);
$user->clearMediaCollection('admin_avatar');
$user->addMediaFromBase64($data->data)
->usingFileName($data->name)
->toMediaCollection('admin_avatar');
}
return new UserResource($user);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\Company\Settings;
use App\Http\Controllers\Controller;
use App\Http\Requests\GetSettingsRequest;
use App\Http\Requests\UpdateSettingsRequest;
use Illuminate\Http\JsonResponse;
class UserSettingsController extends Controller
{
public function show(GetSettingsRequest $request): JsonResponse
{
$user = $request->user();
return response()->json($user->getSettings((array) $request->settings));
}
public function update(UpdateSettingsRequest $request): JsonResponse
{
$user = $request->user();
$user->setSettings($request->settings);
return response()->json([
'success' => true,
]);
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace App\Http\Controllers\Company\Users;
use App\Http\Controllers\Controller;
use App\Http\Requests\DeleteUserRequest;
use App\Http\Requests\UserRequest;
use App\Http\Resources\UserResource;
use App\Models\User;
use App\Services\UserService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class UsersController extends Controller
{
public function __construct(
private readonly UserService $userService,
) {}
/**
* 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(UserRequest $request)
{
$this->authorize('create', User::class);
$user = $this->userService->create($request);
return new UserResource($user);
}
/**
* Display the specified resource.
*
* @return JsonResponse
*/
public function show(User $user)
{
$this->authorize('view', $user);
return new UserResource($user);
}
/**
* Update the specified resource in storage.
*
* @return JsonResponse
*/
public function update(UserRequest $request, User $user)
{
$this->authorize('update', $user);
$this->userService->update($user, $request);
return new UserResource($user);
}
/**
* Display a listing of the resource.
*
* @param Request $request
* @return JsonResponse
*/
public function delete(DeleteUserRequest $request)
{
$this->authorize('delete multiple users', User::class);
if ($request->users) {
$this->userService->delete($request->users);
}
return response()->json([
'success' => true,
]);
}
}