migrate to gtea from bistbucket

This commit is contained in:
2026-03-15 17:08:23 +07:00
commit 129ca2260c
3716 changed files with 566316 additions and 0 deletions

View File

@@ -0,0 +1,276 @@
<?php
namespace App\Abstracts;
use App\Entity\Dropdown;
use App\Interfaces\CrudInterface;
use App\Interfaces\DBPrepareInterface;
use App\Traits\Authenticatable;
use Carbon\Carbon;
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\Response;
abstract class EntityRepository implements CrudInterface, DBPrepareInterface
{
use Authenticatable;
protected const MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE = 'item_does_not_exist';
protected const MESSAGE_ITEM_COULD_NOT_BE_DELETED = 'item_could_not_be_deleted';
protected string $table;
protected string $primaryKey = 'id';
protected array $messages = [];
protected array $fillableColumns = [];
/**
* @throws Exception
*/
public function getAll(array $filterData = []): Paginator
{
try {
$filter = $this->getFilterData($filterData);
$query = $this->getQuery();
if (isset($filter['search']) && strlen($filter['search']) > 0) {
$query = $this->filterSearchQuery($query, $filter['search']);
}
return $query->orderBy($filter['orderBy'], $filter['order'])
->paginate($filter['perPage']);
} catch (Exception $exception) {
throw new Exception($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
protected function getFilterData(array $filterData = []): array
{
$defaultArgs = [
'perPage' => 10,
'search' => '',
'orderBy' => "{$this->table}.{$this->primaryKey}",
'order' => 'desc',
];
return array_merge($defaultArgs, $filterData);
}
protected function getQuery(): Builder
{
if (
$this->table == 'restaurants' ||
$this->table == 'restaurant_image_s_a_a_s_settings' ||
$this->table == 's_a_a_s_faqs' ||
$this->table == 's_a_a_s_subscriptions' ||
$this->table == 'support_tickets' ||
$this->table == 'support_ticket_categories' ||
$this->table == 'support_ticket_conversations' ||
$this->table == 'support_ticket_f_a_q_s' ||
$this->table == 'support_ticket_ip_infos' ||
$this->table == 'support_ticket_last_conversations' ||
$this->table == 'support_ticket_licenses'
) {
return DB::table($this->table);
}
$restaurantId = (int) $this->getCurrentRestaurantId();
return DB::table($this->table)
->where("{$this->table}.restaurant_id", $restaurantId);
}
protected function filterSearchQuery(Builder|EloquentBuilder $query, string $searchedText): Builder|EloquentBuilder
{
return $query;
}
public function getCount(array $filterData = []): int
{
return $this->getQuery()->count();
}
/**
* @throws Exception
*/
public function create(array $data): bool|object
{
$data = $this->prepareForDB($data);
try {
return $this->getQuery()->insert($data);
} catch (Exception $exception) {
throw new Exception($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* @throws Exception
*/
public function update(int $id, array $data): ?object
{
$item = $this->getById($id);
try {
$data = $this->prepareForDB($data, $item);
$updated = $this->getQuery()
->where("{$this->table}.{$this->primaryKey}", $id)
->update($data);
if ($updated) {
$item = $this->getById($id);
}
return $item;
} catch (Exception $exception) {
throw new Exception($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* @throws Exception
*/
public function getById(int $id, array $selects = ['*']): ?object
{
return $this->getByColumn("{$this->table}.{$this->primaryKey}", $id, $selects);
}
public function prepareForDB(array $data, ?object $item = null): array
{
if (count($this->fillableColumns) === 0) {
return $data;
}
$preparedData = [];
foreach ($this->fillableColumns as $column) {
if (isset($data[$column])) {
$preparedData[$column] = $data[$column] ?? null;
}
}
return $preparedData;
}
/**
* @throws Exception
*/
public function getByColumn(string $columnName, $columnValue, array $selects = ['*']): ?object
{
try {
$item = $this->getQuery()
->where($columnName, $columnValue)
->select($selects)
->first();
if (empty($item)) {
throw new Exception(
$this->getExceptionMessage(static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE),
Response::HTTP_NOT_FOUND
);
}
return $item;
} catch (Exception $exception) {
throw new Exception($exception);
}
}
protected function getExceptionMessage(string $key): string
{
return $this->getExceptionMessages()[$key];
}
protected function getExceptionMessages(): array
{
return [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'Item does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'Item could not be deleted.',
];
}
/**
* @throws Exception
*/
public function delete(int $id): ?object
{
$item = $this->getById($id);
try {
$deleted = $this->getQuery()
->where($this->primaryKey, $id)
->update([
'deleted_at' => Carbon::now(),
]);
} catch (Exception $exception) {
throw new Exception($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
if (! $deleted) {
throw new Exception(
$this->getExceptionMessage(static::MESSAGE_ITEM_COULD_NOT_BE_DELETED),
Response::HTTP_INTERNAL_SERVER_ERROR
);
}
return $item;
}
/**
* @throws Exception
*/
public function getDropdown(): array
{
$columns = array_unique(
array_merge(
[$this->primaryKey],
$this->getDropdownSelectableColumns()
)
);
$dropdown = (new Dropdown)
->setTableName($this->table)
->setSelectedColumns($columns)
->setOrderByColumnWithOrders($this->getOrderByColumnWithOrders())
->setSelectableTableQuery();
$dropdown->setQuery($this->getDropdownAdditionalWhere($dropdown->getQuery()));
return $dropdown->getDropdowns();
}
/**
* @throws Exception
*/
protected function getDropdownSelectableColumns(): array
{
throw new Exception('This method must be called from child repository.');
}
/**
* @throws Exception
*/
protected function getOrderByColumnWithOrders(): array
{
throw new Exception('This method must be called from child repository.');
}
protected function getDropdownAdditionalWhere(Builder $query): Builder
{
return $query;
}
protected function getNextAutoIncrementId(): int
{
$tableInfo = DB::select("SHOW CREATE TABLE {$this->table}");
$tableCreateStatement = $tableInfo[0]->{'Create Table'};
preg_match('/AUTO_INCREMENT=(\d+)/', $tableCreateStatement, $matches);
return $matches[1] ?? 1;
}
}

View File

@@ -0,0 +1,217 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\GeneratorCommand;
use Illuminate\Support\Str;
use Symfony\Component\Console\Input\InputOption;
class GenerateCrudServiceCommand extends GeneratorCommand
{
protected $name = 'make:crud';
protected $description = 'Generate CRUD operations including controller, requests, service, interface, views, and routes for a given model';
public function handle(): bool
{
$model = $this->argument('model');
$path = $this->option('path') ?? '';
// Generate model and migration
// $this->generateModelWithMigration($model, $path);
// Generate controller
$this->generateController($model, $path = 'WEB');
// Generate request classes
$this->generateRequestClass($model, 'Store', $path = $model);
$this->generateRequestClass($model, 'Update', $path = $model);
// Generate service and interface
$this->generateService($model, $path = $model);
$this->generateInterface($model, $path = $model);
$this->generateDataTable($model, $path = $model);
// Generate views
$this->generateView($model, 'index');
$this->generateView($model, 'create');
$this->generateView($model, 'edit');
// Generate route file
$this->generateRouteFile($model);
// Now, add API specific generation
// $this->generateApiController($model);
return true;
}
// protected function generateModelWithMigration($model, $path)
// {
// $namespace = $path ? Str::replace('/', '\\', $path) : '';
// $this->call('make:model', [
// 'name' => ($namespace ? $namespace . '\\' : '') . $model,
// '--migration' => true, // generate migration automatically
// ]);
// }
protected function generateController($model, $path)
{
$controllerName = "{$model}Controller";
$namespace = 'App\\Http\\Controllers\\'.($path ? Str::replace('/', '\\', $path).'\\' : '');
$this->call('make:controller', [
'name' => $namespace.$controllerName,
'--model' => $model,
'--path' => $path,
]);
}
protected function generateApiController($model)
{
$controllerName = "{$model}Controller";
$namespace = 'App\\Http\\Controllers\\API';
$controllerPath = app_path("Http/Controllers/API/{$controllerName}.php");
// Load the custom API controller stub
$stub = file_get_contents(resource_path('stubs/api-controller.stub'));
// Replace placeholder values in the stub
$content = str_replace(
['DummyNamespace', 'DummyFullModelClass', 'DummyClass', 'DummyModel', 'model'],
[
$namespace,
"App\\Models\\{$model}",
"{$model}Controller",
"{$model}",
strtolower($model),
],
$stub
);
// Write the controller file to the existing directory
file_put_contents($controllerPath, $content);
}
protected function generateRequestClass($model, $action, $path)
{
$name = "{$model}{$action}Request";
$namespace = 'App\\Http\\Requests\\'.($path ? Str::replace('/', '\\', $path).'\\' : '');
$this->call('make:request', [
'name' => $namespace.$name,
]);
}
protected function generateInterface($model, $path)
{
$interfaceName = "{$model}Interface";
$namespace = 'App\\Interfaces\\'.($path ? Str::replace('/', '\\', $path).'\\' : '');
$this->call('make:interface-file', [
'name' => $namespace.$interfaceName,
]);
}
protected function generateService($model, $path)
{
$serviceName = "{$model}Service";
$namespace = 'App\\Services\\'.($path ? Str::replace('/', '\\', $path).'\\' : '');
$this->call('make:service-file', [
'name' => $namespace.$serviceName,
'--model' => $model,
]);
}
protected function generateDataTable($model, $path)
{
$dataTableName = "{$model}DataTable";
$namespace = 'App\\DataTables\\'.($path ? Str::replace('/', '\\', $path).'\\' : '');
$this->call('make:datatable-file', [
'name' => $namespace.$dataTableName,
'--model' => $model,
]);
}
protected function generateView($model, $view)
{
$modelLower = strtolower($model);
$viewPath = resource_path("views/backend/{$modelLower}/{$view}.blade.php");
if (! file_exists($viewPath)) {
$this->makeDirectory($viewPath);
file_put_contents($viewPath, $this->getViewStub($view, $model));
}
}
protected function getViewStub($view, $model)
{
$stub = file_get_contents(resource_path("stubs/{$view}.stub"));
// Replace DummyModel with the actual model name
return str_replace(['DummyModel', 'model'], [$model, strtolower($model)], $stub);
}
protected function generateRouteFile($model)
{
$modelLower = strtolower($model);
$webRouteFile = base_path('routes/web.php');
// $apiRouteFile = base_path('routes/api.php');
// Web route content
$webContent = <<<EOL
Route::resource('{$modelLower}s', App\Http\Controllers\\WEB\\{$model}Controller::class);
EOL;
// API route content
// $apiContent = <<<EOL
// Route::get('all-{$modelLower}', [App\Http\Controllers\API\\{$model}Controller::class, 'all']);
// Route::apiResource('{$modelLower}s', App\Http\Controllers\API\\{$model}Controller::class);
// EOL;
// Include in web.php
$this->includeInFile($webRouteFile, $webContent);
// Include in api.php
// $this->includeInFile($apiRouteFile, $apiContent);
}
protected function includeInFile($filePath, $content)
{
$includeStatement = trim($content);
// Ensure the file doesn't already include the content
$currentContent = file_get_contents($filePath);
if (strpos($currentContent, $includeStatement) === false) {
file_put_contents($filePath, PHP_EOL.$includeStatement.PHP_EOL, FILE_APPEND);
}
}
protected function getStub()
{
return '';
}
protected function getDefaultNamespace($rootNamespace)
{
return '';
}
protected function getArguments()
{
return [
['model', InputOption::VALUE_REQUIRED, 'The name of the model'],
];
}
protected function getOptions()
{
return [
['path', null, InputOption::VALUE_OPTIONAL, 'The optional path for the generated files'],
];
}
}

View File

@@ -0,0 +1,377 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
class MakeModuleEntity extends Command
{
protected $signature = 'make:module-entity {module} {name}';
protected $description = 'Create module-based model, controller, repository, request, migration, and route';
public function handle()
{
$module = Str::studly($this->argument('module'));
$name = Str::studly($this->argument('name'));
$pluralName = Str::pluralStudly($name);
$snakePlural = Str::snake($pluralName);
$basePath = base_path("Modules/{$module}");
$paths = [
'model' => "{$basePath}/app/Models/{$name}.php",
'controller' => "{$basePath}/app/Http/Controllers/API/{$name}Controller.php",
'storeRequest' => "{$basePath}/app/Http/Requests/{$name}/{$name}StoreRequest.php",
'updateRequest' => "{$basePath}/app/Http/Requests/{$name}/{$name}UpdateRequest.php",
'repository' => "{$basePath}/Repositories/{$name}Repository.php",
'migration' => "{$basePath}/Database/Migrations/".date('Y_m_d_His')."_create_{$snakePlural}_table.php",
'route' => "{$basePath}/routes/api.php",
];
// Make directories
foreach ($paths as $path) {
File::ensureDirectoryExists(dirname($path));
}
// Generate Model with Fillable Template
File::put($paths['model'], "<?php
namespace Modules\\{$module}\\Models;
use Illuminate\Database\Eloquent\Model;
class {$name} extends Model
{
protected \$fillable = [
'restaurant_id',
'name',
'status',
'created_at',
'updated_at',
'deleted_at'
];
public const TABLE_NAME = '".$snakePlural."';
protected \$table = self::TABLE_NAME;
}");
// Controller with extended functionality
File::put($paths['controller'], "<?php
namespace Modules\\{$module}\\Http\\Controllers\\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\\{$module}\\Http\\Requests\\{$name}\\{$name}StoreRequest;
use Modules\\{$module}\\Http\\Requests\\{$name}\\{$name}UpdateRequest;
use Modules\\{$module}\\Repositories\\{$name}Repository;
class {$name}Controller extends Controller
{
public function __construct(private {$name}Repository \$repo) {}
public function index(): JsonResponse
{
try {
return \$this->responseSuccess(\$this->repo->getAll(request()->all()),'{$name} has been fetched successfully.');
} catch (Exception \$e) {
return \$this->responseError([], \$e->getMessage());
}
}
public function store({$name}StoreRequest \$request): JsonResponse
{
try {
return \$this->responseSuccess(\$this->repo->create(\$request->all()),'{$name} has been created successfully.');
} catch (\Illuminate\Database\QueryException \$exception) {
return \$this->responseError([], 'Database error: '.\$exception->getMessage());
} catch (Exception \$e) {
return \$this->responseError([], \$e->getMessage());
}
}
public function show(\$id): JsonResponse
{
try {
return \$this->responseSuccess(\$this->repo->getById(\$id), '{$name} has been fetched successfully.');
} catch (Exception \$e) {
return \$this->responseError([], \$e->getMessage());
}
}
public function update({$name}UpdateRequest \$request, \$id): JsonResponse
{
try {
return \$this->responseSuccess(\$this->repo->update(\$id, \$request->all()),'{$name} has been updated successfully.');
} catch (Exception \$e) {
return \$this->responseError([], \$e->getMessage());
}
}
public function destroy(\$id): JsonResponse
{
try {
return \$this->responseSuccess(\$this->repo->delete(\$id),'{$name} has been deleted successfully.');
} catch (Exception \$e) {
return \$this->responseError([], \$e->getMessage());
}
}
}");
// Generate Migration Content
File::put($paths['migration'], "<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('{$snakePlural}', function (Blueprint \$table) {
\$table->id();
\$table->unsignedBigInteger('restaurant_id')->nullable();
\$table->string('name');
\$table->smallInteger('status')->default(1)->comment('1=Active, 2=InActive');
\$table->timestamps();
\$table->softDeletes();
});
}
public function down(): void
{
Schema::dropIfExists('{$snakePlural}');
}
};
");
// Request classes
File::put($paths['storeRequest'], "<?php
namespace Modules\\{$module}\\Http\\Requests\\{$name};
use Illuminate\Foundation\Http\FormRequest;
class {$name}StoreRequest extends FormRequest
{
public function rules(): array
{
return [
// validation rules
];
}
public function authorize(): bool
{
return true;
}
}");
File::put($paths['updateRequest'], "<?php
namespace Modules\\{$module}\\Http\\Requests\\{$name};
use Illuminate\Foundation\Http\FormRequest;
class {$name}UpdateRequest extends FormRequest
{
public function rules(): array
{
return [
// validation rules
];
}
public function authorize(): bool
{
return true;
}
}");
// Repository with custom logic
File::put($paths['repository'], "<?php
namespace Modules\\{$module}\\Repositories;
use App\Abstracts\EntityRepository;
use Modules\\{$module}\\Models\\{$name};
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Symfony\Component\HttpFoundation\Response;
class {$name}Repository extends EntityRepository
{
public string \$table = {$name}::TABLE_NAME;
protected array \$fillableColumns = [
'restaurant_id',
'name',
'status',
'created_by',
'created_at',
'updated_at',
'deleted_at'
];
protected function getQuery(): Builder
{
return parent::getQuery();
}
protected function getFilterData(array \$filterData = []): array
{
\$defaultArgs = [
'perPage' => 10,
'search' => '',
'orderBy' => 'id',
'order' => 'desc',
'with_deleted' => false,
];
return array_merge(\$defaultArgs, \$filterData);
}
private function get{$name}Query(): Builder
{
return \$this->getQuery()
->select(
\"{\$this->table}.id\",
\"{\$this->table}.restaurant_id\",
\"{\$this->table}.name\",
\"{\$this->table}.status\",
\"{\$this->table}.created_at\",
\"{\$this->table}.deleted_at\"
);
}
protected function filterSearchQuery(Builder|EloquentBuilder \$query, string \$searchedText): Builder
{
\$searchable = \"%\$searchedText%\";
return \$query->where(\"{\$this->table}.name\", 'LIKE', \$searchable)
->orWhere(\"{\$this->table}.status\", 'LIKE', \$searchable);
}
public function getAll(array \$filterData = []): Paginator
{
\$filter = \$this->getFilterData(\$filterData);
\$query = \$this->get{$name}Query();
if (! \$filter['with_deleted']) {
\$query->whereNull(\"{\$this->table}.deleted_at\");
}
if (!empty(\$filter['search'])) {
\$query = \$this->filterSearchQuery(\$query, \$filter['search']);
}
return \$query
->orderBy(\$filter['orderBy'], \$filter['order'])
->paginate(\$filter['perPage']);
}
public function getCount(array \$filterData = []): int
{
\$filter = \$this->getFilterData(\$filterData);
\$query = \$this->getQuery();
if (! \$filter['with_deleted']) {
\$query->whereNull(\"{\$this->table}.deleted_at\");
}
return \$query->count();
}
/**
* @throws Exception
*/
public function getByColumn(string \$columnName, \$columnValue, array \$selects = ['*']): ?object
{
\$item = \$this->get{$name}Query()
->where(\$columnName, \$columnValue)
->first(\$selects);
if (empty(\$item)) {
throw new Exception(
\$this->getExceptionMessage(static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE),
Response::HTTP_NOT_FOUND
);
}
return \$item;
}
/**
* @throws Exception
*/
public function create(array \$data): object
{
\$data = \$this->prepareForDB(\$data);
\$id = \$this->getQuery()->insertGetId(\$data);
return {$name}::find(\$id);
}
/**
* @throws Exception
*/
public function update(int \$id, array \$data): object
{
\$item = {$name}::findOrFail(\$id);
\$data = \$this->prepareForDB(\$data, \$item);
parent::update(\$id, \$data);
return \$this->getById(\$id);
}
/**
* @throws Exception
*/
public function prepareForDB(array \$data, ?object \$item = null): array
{
\$data = parent::prepareForDB(\$data, \$item);
if (empty(\$item)) {
\$data['created_at'] = now();
\$data['restaurant_id'] = \$this->getCurrentRestaurantId();
\$data['status'] = 1;
if (!empty(\$data['image']) && \$data['image'] instanceof \Illuminate\Http\UploadedFile) {
\$data['image'] = fileUploader('{$module}/', 'png', \$data['image']);
}
} else {
if (!empty(\$data['image']) && \$data['image'] instanceof \Illuminate\Http\UploadedFile) {
\$data['image'] = fileUploader('{$module}/', 'png', \$data['image'], \$item->image);
}
\$data['updated_at'] = now();
}
return \$data;
}
protected function getExceptionMessages(): array
{
return [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => '{$name} does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => '{$name} could not be deleted.',
];
}
}
");
// Create module route
if (! File::exists($paths['route'])) {
File::put($paths['route'], "<?php\n\nuse Illuminate\Support\Facades\Route;\n\n");
}
File::append($paths['route'], "\nRoute::apiResource('".Str::kebab($snakePlural)."', \\Modules\\{$module}\\Http\\Controllers\\API\\{$name}Controller::class);");
// Add module route loader to RouteServiceProvider (if not already done manually)
$this->info("{$name} entity created under module {$module} with full structure!");
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\GeneratorCommand;
use Symfony\Component\Console\Input\InputOption;
class NewActionMakeCommand extends GeneratorCommand
{
protected $name = 'make:action';
protected $description = 'Create a new action class';
protected $type = 'Action';
protected function getStub()
{
return resource_path('stubs/action.stub');
}
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\\Actions';
}
protected function buildClass($name)
{
$stub = parent::buildClass($name);
$model = $this->option('model');
$action = $this->option('action');
return str_replace(
['DummyModel', 'DummyAction'],
[$model, $action],
$stub
);
}
protected function getOptions()
{
return [
['model', null, InputOption::VALUE_REQUIRED, 'The name of the model'],
['action', null, InputOption::VALUE_REQUIRED, 'The action type (Create, Read, Update, Delete)'],
];
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\GeneratorCommand;
use Symfony\Component\Console\Input\InputOption;
class NewControllerMakeCommand extends GeneratorCommand
{
protected $name = 'make:controller';
protected $description = 'Create a new resource controller class';
protected $type = 'Controller';
protected function getStub()
{
return resource_path('stubs/controller.service.stub');
}
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\\Http\\Controllers';
}
protected function buildClass($name)
{
$stub = parent::buildClass($name);
$model = $this->option('model');
$modelLower = strtolower($model);
return str_replace(
['DummyModel', 'model'],
[$model, $modelLower],
$stub
);
}
protected function getOptions()
{
return [
['model', null, InputOption::VALUE_REQUIRED, 'The name of the model'],
['path', null, InputOption::VALUE_OPTIONAL, 'The optional path for the generated files'],
];
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\GeneratorCommand;
use Symfony\Component\Console\Input\InputOption;
class NewDataTableMakeCommand extends GeneratorCommand
{
protected $name = 'make:datatable-file';
protected $description = 'Create a new yajra DataTables class';
protected $type = 'DataTable';
protected function getStub()
{
return resource_path('stubs/datatable.stub');
}
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\\DataTables';
}
protected function buildClass($name)
{
$stub = parent::buildClass($name);
$model = $this->option('model');
$namespaceModel = 'App\\Models\\'.$model;
return str_replace(
['DummyModel', 'DummyModelNamespace'],
[$model, $namespaceModel],
$stub
);
}
protected function getOptions()
{
return [
['model', null, InputOption::VALUE_REQUIRED, 'The name of the model'],
];
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\GeneratorCommand;
class NewInterfaceMakeCommand extends GeneratorCommand
{
protected $name = 'make:interface-file';
protected $description = 'Create a new interface class';
protected $type = 'Interface';
protected function getStub()
{
return resource_path('stubs/interface.stub');
}
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\\Interfaces';
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\GeneratorCommand;
class NewRequestMakeCommand extends GeneratorCommand
{
protected $name = 'make:request';
protected $description = 'Create a new form request class';
protected $type = 'Request';
protected function getStub()
{
return resource_path('stubs/request.stub');
}
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\\Http\\Requests';
}
protected function buildClass($name)
{
$stub = parent::buildClass($name);
return $stub;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Console\Commands;
use App\Jobs\GenerateMonthlyInvoices;
use Illuminate\Console\Command;
class RunInvoiceJobLoop extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'invoice:loop';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run GenerateMonthlyInvoices job every 5 seconds (for testing)';
public function handle()
{
$this->info('Starting job loop... Press Ctrl+C to stop.');
// while (true) {
// dispatch(new GenerateMonthlyInvoices);
// $this->info('Job dispatched at '.now());
// sleep(300); // wait 5 seconds
// }
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\GeneratorCommand;
use Symfony\Component\Console\Input\InputOption;
class ServiceFileMakeCommand extends GeneratorCommand
{
protected $name = 'make:service-file';
protected $description = 'Create a new service class';
protected $type = 'Service';
protected function getStub()
{
return resource_path('stubs/service.stub');
}
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\\Services';
}
protected function buildClass($name)
{
$stub = parent::buildClass($name);
$model = $this->option('model');
$namespaceModel = 'App\\Models\\'.$model;
return str_replace(
['DummyModel', 'DummyModelNamespace'],
[$model, $namespaceModel],
$stub
);
}
protected function getOptions()
{
return [
['model', null, InputOption::VALUE_REQUIRED, 'The name of the model'],
];
}
}

View File

@@ -0,0 +1,180 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Modules\Authentication\Models\Restaurant;
use Modules\Authentication\Models\Setting;
class SyncAllSettingsCommand extends Command
{
protected $signature = 'settings:sync-all';
// php artisan settings:sync-all
protected $description = 'Sync all setting keys for each restaurant if missing';
protected $defaultKeys = [
'restaurant_name',
'site_title',
'phone',
'email',
'language',
'google_map',
'address',
'on_google_map',
'restaurant_code',
'currency_symbol',
'logo',
'mail_type',
'disabled_website',
'copyright_text',
'facebook_link',
'google_plus_link',
'youtube_link',
'whats_app_link',
'twitter_link',
'eiin_code',
'sms_gateway',
'bulk_sms_api_key',
'bulk_sms_sender_id',
'twilio_sid',
'twilio_token',
'twilio_from_number',
'header_notice',
'app_version',
'app_url',
'tagline',
'favicon',
'theme_color',
'background_image',
'tax_type',
'tax_percentage',
'service_charge',
'default_currency',
'billing_prefix',
'invoice_footer',
'enable_kitchen_print',
'enable_customer_copy',
'enable_online_order',
'delivery_charge',
'minimum_order_amount',
'auto_accept_order',
'estimated_preparation_time',
'slack_webhook_url',
'telegram_bot_token',
'telegram_chat_id',
'twilio_sms_enabled',
'email_notifications',
'whatsapp_notifications',
'auto_backup',
'report_timezone',
'data_retention_days',
'sidebar_collapsed',
'dark_mode',
'default_dashboard',
'razorpay_key',
'razorpay_secret',
'stripe_key',
'stripe_secret',
'cash_on_delivery',
'max_table_capacity',
'default_shift_start',
'default_shift_end',
'auto_logout_idle_minutes',
'primary_color',
'secondary_color',
'primary_container_color',
'dark_primary_color',
'dark_secondary_color',
'dark_container_color',
'text_color',
'dark_text_color',
'sidebar_selected_bg_color',
'sidebar_selected_text_color',
'is_online',
'latitude',
'longitude',
'delivery_radius_km',
'delivery_fee',
'delivery_partner_count',
'delivery_time_avg',
'pickup_enabled',
'opening_time',
'closing_time',
'auto_accept_orders',
'pre_order_enabled',
'max_order_capacity',
'avg_rating',
'review_count',
'total_orders',
'last_order_time',
'last_active_time',
'loyalty_points_enabled',
'offers_enabled',
'social_media_links',
'settings',
'uuid',
'email_smtp_host',
'email_smtp_port',
'email_smtp_username',
'email_smtp_password',
'email_smtp_encryption',
'twilio_api_key',
'twilio_api_secret',
'twilio_sender_id',
'twilio_api_url',
'twilio_is_default',
'nexmo_api_key',
'nexmo_api_secret',
'nexmo_sender_id',
'nexmo_api_url',
'nexmo_is_default',
'muthofun_api_key',
'smsglobal_api_key',
'smsglobal_api_secret',
'smsglobal_sender_id',
'smsglobal_api_url',
'smsglobal_extra_key',
'smsglobal_is_default',
];
public function handle()
{
$restaurants = Restaurant::all();
foreach ($restaurants as $restaurant) {
foreach ($this->defaultKeys as $key) {
$extraValue = [];
$defaultValues = [
'restaurant_name' => $extraValue,
];
$setting = Setting::where('restaurant_id', $restaurant->id)
->where('key', $key)
->first();
if (! $setting) {
// Create if it doesn't exist
Setting::create([
'restaurant_id' => $restaurant->id,
'key' => $key,
'value' => $defaultValues[$key] ?? null,
'type' => 'vendor',
'is_active' => true,
]);
$this->info("Added missing setting '{$key}' for Restaurant ID: {$restaurant->id}");
} elseif (is_null($setting->value)) {
// Update only if value is NULL
if (array_key_exists($key, $defaultValues)) {
$setting->update(['value' => $defaultValues[$key]]);
$this->info("Updated '{$key}' for Restaurant ID: {$restaurant->id} because value was null");
}
} else {
// Skip if already exists with a non-null value
$this->line("Setting '{$key}' already exists for Restaurant ID: {$restaurant->id} with non-null value — skipped.");
}
}
}
$this->info('All settings sync complete.');
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Console\Commands\Traits;
use ReflectionClass;
trait ServiceProviderInjector
{
public function injectCodeToRegisterMethod($appServiceProviderFile, $codeToAdd)
{
$reflectionClass = new ReflectionClass(\App\Providers\AppServiceProvider::class);
$reflectionMethod = $reflectionClass->getMethod('register');
$methodBody = file($appServiceProviderFile);
$startLine = $reflectionMethod->getStartLine() - 1;
$endLine = $reflectionMethod->getEndLine() - 1;
array_splice($methodBody, $endLine, 0, $codeToAdd);
$modifiedCode = implode('', $methodBody);
file_put_contents($appServiceProviderFile, $modifiedCode);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
protected function schedule(Schedule $schedule): void
{
// if (app()->environment('local')) {
// } else {
// }
}
}

View File

@@ -0,0 +1,140 @@
<?php
namespace App\DataTables\Onboarding;
use App\Services\Onboarding\OnboardingService;
use Illuminate\Database\Eloquent\Builder as QueryBuilder;
use Modules\Frontend\Models\Onboarding;
use Yajra\DataTables\EloquentDataTable;
use Yajra\DataTables\Html\Builder as HtmlBuilder;
use Yajra\DataTables\Html\Column;
use Yajra\DataTables\Services\DataTable;
class OnboardingDataTable extends DataTable
{
public function __construct(
private OnboardingService $service
) {}
public function dataTable(QueryBuilder $query): EloquentDataTable
{
return (new EloquentDataTable($query))
->addIndexColumn()
->addColumn('action', function (Onboarding $model) {
$html = '<button type="button"
class="btn btn-icon btn-bg-light btn-active-color-primary btn-sm assign-package-btn"
data-id="'.$model->id.'"
data-name="'.e($model->name).'"
data-restaurant="'.e($model->restaurant_name).'">
<i class="bi bi-box-seam"></i>
</button>';
$html .= '<a class="btn btn-icon btn-bg-light btn-active-color-primary btn-sm me-1 edit-icon" href="'.route('onboardings.edit', $model->id).'">
<span class="svg-icon svg-icon-3">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M21.4 8.35303L19.241 10.511L13.485 4.755L15.643 2.59595C16.0248 2.21423 16.5426 1.99988 17.0825 1.99988C17.6224 1.99988 18.1402 2.21423 18.522 2.59595L21.4 5.474C21.7817 5.85581 21.9962 6.37355 21.9962 6.91345C21.9962 7.45335 21.7817 7.97122 21.4 8.35303ZM3.68699 21.932L9.88699 19.865L4.13099 14.109L2.06399 20.309C1.98815 20.5354 1.97703 20.7787 2.03189 21.0111C2.08674 21.2436 2.2054 21.4561 2.37449 21.6248C2.54359 21.7934 2.75641 21.9115 2.989 21.9658C3.22158 22.0201 3.4647 22.0084 3.69099 21.932H3.68699Z" fill="currentColor"></path>
<path d="M5.574 21.3L3.692 21.928C3.46591 22.0032 3.22334 22.0141 2.99144 21.9594C2.75954 21.9046 2.54744 21.7864 2.3789 21.6179C2.21036 21.4495 2.09202 21.2375 2.03711 21.0056C1.9822 20.7737 1.99289 20.5312 2.06799 20.3051L2.696 18.422L5.574 21.3ZM4.13499 14.105L9.891 19.861L19.245 10.507L13.489 4.75098L4.13499 14.105Z" fill="currentColor"></path>
</svg>
</span>
</a>';
$html .= '<button type="button" class="btn btn-icon btn-bg-light btn-active-color-primary btn-sm delete-icon" data-bs-toggle="modal" data-bs-target="#deleteOnboarding'.$model->id.'">
<span class="svg-icon svg-icon-3">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 9C5 8.44772 5.44772 8 6 8H18C18.5523 8 19 8.44772 19 9V18C19 19.6569 17.6569 21 16 21H8C6.34315 21 5 19.6569 5 18V9Z" fill="currentColor"></path>
<path opacity="0.5" d="M5 5C5 4.44772 5.44772 4 6 4H18C18.5523 4 19 4.44772 19 5V5C19 5.55228 18.5523 6 18 6H6C5.44772 6 5 5.55228 5 5V5Z" fill="currentColor"></path>
<path opacity="0.5" d="M9 4C9 3.44772 9.44772 3 10 3H14C14.5523 3 15 3.44772 15 4V4H9V4Z" fill="currentColor"></path>
</svg>
</span>
</button>';
$html .= '
<div class="modal fade" id="deleteOnboarding'.$model->id.'" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Are you sure?</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>
Are you sure you want to delete the Onboarding - <b>'.$model->restaurant_name.'</b>?
</p>
<form action="'.route('onboardings.destroy', $model->id).'" method="POST">
'.csrf_field().method_field('DELETE').'
<button type="submit" class="btn btn-danger">Confirm Delete</button>
</form>
</div>
</div>
</div>
</div>
';
return $html;
})
->editColumn('status', function (Onboarding $model) {
$statusClasses = [
'pending' => 'badge bg-warning text-dark',
'in_progress' => 'badge bg-info text-dark',
'documents_submitted' => 'badge bg-primary',
'approved' => 'badge bg-success',
'rejected' => 'badge bg-danger',
'hold' => 'badge bg-secondary',
];
$statusText = ucfirst(str_replace('_', ' ', $model->status));
$class = $statusClasses[$model->status] ?? 'badge bg-dark';
return '<span class="'.$class.'">'.$statusText.'</span>';
})
->rawColumns(['action', 'status']);
}
public function query(Onboarding $model): QueryBuilder
{
return $this->service->get(['is_query' => true]);
}
public function html(): HtmlBuilder
{
return $this->builder()
->setTableId('Onboarding-table')
->columns($this->getColumns())
->minifiedAjax()
->orderBy(1)
->selectStyleSingle()
->buttons([
// Button::make('add'),
// Button::make('excel'),
// Button::make('csv'),
// Button::make('pdf'),
// Button::make('print'),
// Button::make('reset'),
// Button::make('reload'),
]);
}
public function getColumns(): array
{
return [
Column::make('DT_RowIndex')->title('Sl no')->searchable(false)->orderable(false),
Column::make('name')->title('Owner Name'),
Column::make('restaurant_name')->title('Restaurant Name'),
Column::make('phone')->title('Phone'),
Column::make('restaurant_domain')->title('Domain'),
Column::make('restaurant_address')->title('Address'),
Column::make('restaurant_type')->title('Business Type'),
Column::make('status')->title('Status'),
Column::make('action')
->title('Action')
->searchable(false)
->orderable(false)
->printable(false),
];
}
protected function filename(): string
{
return 'Onboarding-'.date('YmdHis');
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace App\DataTables\Package;
use App\Services\Package\PackageService;
use Illuminate\Database\Eloquent\Builder as QueryBuilder;
use Modules\Authentication\Models\Package;
use Yajra\DataTables\EloquentDataTable;
use Yajra\DataTables\Html\Builder as HtmlBuilder;
use Yajra\DataTables\Html\Column;
use Yajra\DataTables\Services\DataTable;
class PackageDataTable extends DataTable
{
public function __construct(
private PackageService $service
) {}
public function dataTable(QueryBuilder $query): EloquentDataTable
{
return (new EloquentDataTable($query))
->addIndexColumn()
->addColumn('action', function (Package $model) {
$html = '<a class="btn btn-icon btn-bg-light btn-active-color-primary btn-sm me-1 edit-icon" href="'.route('packages.edit', $model->id).'">
<span class="svg-icon svg-icon-3">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.3" d="M21.4 8.35303L19.241 10.511L13.485 4.755L15.643 2.59595C16.0248 2.21423 16.5426 1.99988 17.0825 1.99988C17.6224 1.99988 18.1402 2.21423 18.522 2.59595L21.4 5.474C21.7817 5.85581 21.9962 6.37355 21.9962 6.91345C21.9962 7.45335 21.7817 7.97122 21.4 8.35303ZM3.68699 21.932L9.88699 19.865L4.13099 14.109L2.06399 20.309C1.98815 20.5354 1.97703 20.7787 2.03189 21.0111C2.08674 21.2436 2.2054 21.4561 2.37449 21.6248C2.54359 21.7934 2.75641 21.9115 2.989 21.9658C3.22158 22.0201 3.4647 22.0084 3.69099 21.932H3.68699Z" fill="currentColor"></path>
<path d="M5.574 21.3L3.692 21.928C3.46591 22.0032 3.22334 22.0141 2.99144 21.9594C2.75954 21.9046 2.54744 21.7864 2.3789 21.6179C2.21036 21.4495 2.09202 21.2375 2.03711 21.0056C1.9822 20.7737 1.99289 20.5312 2.06799 20.3051L2.696 18.422L5.574 21.3ZM4.13499 14.105L9.891 19.861L19.245 10.507L13.489 4.75098L4.13499 14.105Z" fill="currentColor"></path>
</svg>
</span>
</a>';
$html .= '<button type="button" class="btn btn-icon btn-bg-light btn-active-color-primary btn-sm delete-icon" data-bs-toggle="modal" data-bs-target="#deletePackage'.$model->id.'">
<span class="svg-icon svg-icon-3">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 9C5 8.44772 5.44772 8 6 8H18C18.5523 8 19 8.44772 19 9V18C19 19.6569 17.6569 21 16 21H8C6.34315 21 5 19.6569 5 18V9Z" fill="currentColor"></path>
<path opacity="0.5" d="M5 5C5 4.44772 5.44772 4 6 4H18C18.5523 4 19 4.44772 19 5V5C19 5.55228 18.5523 6 18 6H6C5.44772 6 5 5.55228 5 5V5Z" fill="currentColor"></path>
<path opacity="0.5" d="M9 4C9 3.44772 9.44772 3 10 3H14C14.5523 3 15 3.44772 15 4V4H9V4Z" fill="currentColor"></path>
</svg>
</span>
</button>';
$html .= '
<div class="modal fade" id="deletePackage'.$model->id.'" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Are you sure?</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>
Are you sure you want to delete the Package - <b>'.$model->name.'</b>?
</p>
<form action="'.route('packages.destroy', $model->id).'" method="POST">
'.csrf_field().method_field('DELETE').'
<button type="submit" class="btn btn-danger">Confirm Delete</button>
</form>
</div>
</div>
</div>
</div>
';
return $html;
})
->rawColumns(['action']);
}
public function query(Package $model): QueryBuilder
{
return $this->service->get(['is_query' => true]);
}
public function html(): HtmlBuilder
{
return $this->builder()
->setTableId('Package-table')
->columns($this->getColumns())
->minifiedAjax()
->orderBy(1)
->selectStyleSingle()
->buttons([
// Button::make('add'),
// Button::make('excel'),
// Button::make('csv'),
// Button::make('pdf'),
// Button::make('print'),
// Button::make('reset'),
// Button::make('reload'),
]);
}
public function getColumns(): array
{
return [
Column::make('DT_RowIndex')->title('Sl no')->searchable(false)->orderable(false),
Column::make('name')->title('Name'),
Column::make('price')->title('Price'),
Column::make('package_type')->title('Package Type'),
Column::make('audience_type')->title('Audience Type'),
Column::make('duration')->title('Duration'),
Column::make('action')
->title('Action')
->searchable(false)
->orderable(false)
->printable(false),
];
}
protected function filename(): string
{
return 'Package-'.date('YmdHis');
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace App\DataTables\Restaurant;
use App\Services\Restaurant\RestaurantService;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder as QueryBuilder;
use Modules\Authentication\Models\Package;
use Modules\Authentication\Models\Restaurant;
use Yajra\DataTables\EloquentDataTable;
use Yajra\DataTables\Html\Builder as HtmlBuilder;
use Yajra\DataTables\Html\Column;
use Yajra\DataTables\Services\DataTable;
class RestaurantDataTable extends DataTable
{
public function __construct(
private RestaurantService $service
) {}
public function dataTable(QueryBuilder $query): EloquentDataTable
{
return (new EloquentDataTable($query))
->addIndexColumn()
->addColumn('action', function (Restaurant $model) {
$html = '';
// Upgrade button
$html .= '<button type="button" class="btn btn-icon btn-bg-light btn-active-color-primary btn-sm me-1"
data-bs-toggle="modal" data-bs-target="#upgradeSubscriptionModal'.$model->id.'">
<i class="bi bi-arrow-up-circle"></i> Upgrade
</button>';
// Upgrade Modal
$html .= '
<div class="modal fade" id="upgradeSubscriptionModal'.$model->id.'" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<form action="'.route('restaurants.upgradeSubscription', $model->id).'" method="POST">
'.csrf_field().'
<div class="modal-header">
<h5 class="modal-title">Upgrade Subscription - '.$model->restaurant_name.'</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<label for="package_id">Select Package</label>
<select name="package_id" class="form-select" required>
<option value="">-- Select Package --</option>';
$packages = Package::orderByDesc('id')->select('id', 'name', 'price')->get();
foreach ($packages as $package) {
$html .= '<option value="'.$package->id.'" data-price="'.$package->price.'">'
.$package->name.' - '.$package->price.' BDT</option>';
}
$html .= '</select>
<label for="custom_amount" class="mt-3">Custom Amount (optional)</label>
<input type="number" name="custom_amount" class="form-control" placeholder="Enter custom amount if any" step="0.01">
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Upgrade</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
</div>
</form>
</div>
</div>
</div>';
// Edit button
$html .= '<a class="btn btn-icon btn-bg-light btn-active-color-primary btn-sm me-1 edit-icon" href="'.route('restaurants.edit', $model->id).'">
<i class="bi bi-pencil"></i>
</a>';
// Delete button
$html .= '<button type="button" class="btn btn-icon btn-bg-light btn-active-color-primary btn-sm delete-icon" data-bs-toggle="modal" data-bs-target="#deleteRestaurant'.$model->id.'">
<i class="bi bi-trash"></i>
</button>';
// Delete Modal
$html .= '
<div class="modal fade" id="deleteRestaurant'.$model->id.'" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Are you sure?</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>Are you sure you want to delete the Restaurant - <b>'.$model->restaurant_name.'</b>?</p>
<form action="'.route('restaurants.destroy', $model->id).'" method="POST">
'.csrf_field().method_field('DELETE').'
<button type="submit" class="btn btn-danger">Confirm Delete</button>
</form>
</div>
</div>
</div>
</div>';
return $html;
})
->addColumn('subscription', function (Restaurant $model) {
if ($model->subscription) {
$start = Carbon::parse($model->subscription->start_date)->format('d M Y');
$end = Carbon::parse($model->subscription->end_date)->format('d M Y');
return '<strong>'.$model->subscription->package?->name.'</strong><br>
<small>Start: '.$start.'</small><br>
<small>End: '.$end.'</small>';
}
return '<span class="text-muted">No subscription</span>';
})
->rawColumns(['subscription', 'action']);
}
public function query(Restaurant $model): QueryBuilder
{
return $this->service->get(['is_query' => true]);
}
public function html(): HtmlBuilder
{
return $this->builder()
->setTableId('Restaurant-table')
->columns($this->getColumns())
->minifiedAjax()
->orderBy(1)
->selectStyleSingle();
}
public function getColumns(): array
{
return [
Column::make('DT_RowIndex')->title('Sl no')->searchable(false)->orderable(false),
Column::make('name')->title('Owner Name'),
Column::make('name')->title('Restaurant Name'),
Column::make('phone')->title('Phone'),
Column::make('domain')->title('Domain'),
Column::make('address')->title('Address'),
Column::make('restaurant_type')->title('Business Type'),
Column::make('subscription')->title('Subscription')->searchable(false)->orderable(false),
Column::make('action')->title('Action')->searchable(false)->orderable(false),
];
}
protected function filename(): string
{
return 'Restaurant-'.date('YmdHis');
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace App\Entity;
use App\Interfaces\DropdownInterface;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
class Dropdown implements DropdownInterface
{
protected string $tableName;
protected string $primaryColumn;
protected array $selectedColumns = [];
protected array $orderByColumnWithOrders = [];
private Builder $query;
public function setTableName(string $tableName): self
{
$this->tableName = $tableName;
return $this;
}
public function setPrimaryColumn(string $primaryColumn): self
{
$this->primaryColumn = $primaryColumn;
return $this;
}
public function setSelectedColumns(array $selectedColumns): self
{
$this->selectedColumns = $selectedColumns;
return $this;
}
public function setOrderByColumnWithOrders(array $orderByColumnWithOrders): self
{
$this->orderByColumnWithOrders = $orderByColumnWithOrders;
return $this;
}
public function getQuery(): Builder
{
return $this->query;
}
public function setQuery(Builder $query): self
{
$this->query = $query;
return $this;
}
public function setSelectableTableQuery(): self
{
$this->query = DB::table($this->tableName)
->select($this->selectedColumns);
return $this;
}
public function getDropdowns(): array
{
if (count($this->orderByColumnWithOrders)) {
foreach ($this->orderByColumnWithOrders as $column => $columnOrder) {
$this->query->orderBy($column, $columnOrder);
}
}
return $this->query->get()->toArray();
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Entity;
class FilePath
{
public const PATH_USER_FILES = 'public/users/';
public const PATH_USER_AVATAR = self::PATH_USER_FILES.'avatars/';
}

View File

@@ -0,0 +1,117 @@
<?php
namespace App\Enum;
enum ActionStatus: string
{
case Create = 'create';
case Update = 'update';
case View = 'view';
case Delete = 'delete';
case OrderCreate = 'order_create';
case OrderUpdate = 'order_update';
case OrderCancel = 'order_cancel';
case OrderStatusUpdate = 'order_status_update';
case PaymentSuccess = 'payment_success';
case PaymentFailed = 'payment_failed';
case RefundIssued = 'refund_issued';
case KitchenAccept = 'kitchen_accept';
case KitchenPreparing = 'kitchen_preparing';
case KitchenReady = 'kitchen_ready';
case KitchenServed = 'kitchen_served';
case PrintReceipt = 'print_receipt';
case ReportGenerate = 'report_generate';
case Login = 'login';
case Logout = 'logout';
case StatusChange = 'status_change'; // generic status updates
// Optional constants
public const CREATE = 'create';
public const UPDATE = 'update';
public const VIEW = 'view';
public const DELETE = 'delete';
public const ORDER_CREATE = 'order_create';
public const ORDER_UPDATE = 'order_update';
public const ORDER_CANCEL = 'order_cancel';
public const ORDER_STATUS_UPDATE = 'order_status_update';
public const PAYMENT_SUCCESS = 'payment_success';
public const PAYMENT_FAILED = 'payment_failed';
public const REFUND_ISSUED = 'refund_issued';
public const KITCHEN_ACCEPT = 'kitchen_accept';
public const KITCHEN_PREPARING = 'kitchen_preparing';
public const KITCHEN_READY = 'kitchen_ready';
public const KITCHEN_SERVED = 'kitchen_served';
public const PRINT_RECEIPT = 'print_receipt';
public const REPORT_GENERATE = 'report_generate';
public const LOGIN = 'login';
public const LOGOUT = 'logout';
public const STATUS_CHANGE = 'status_change';
/**
* Return all action values
*/
public static function values(): array
{
return array_column(self::cases(), 'value');
}
/**
* Human-friendly labels for UI
*/
public static function labels(): array
{
return [
self::Create->value => 'Create',
self::Update->value => 'Update',
self::View->value => 'View',
self::Delete->value => 'Delete',
self::OrderCreate->value => 'Order Created',
self::OrderUpdate->value => 'Order Updated',
self::OrderCancel->value => 'Order Cancelled',
self::OrderStatusUpdate->value => 'Order Status Updated',
self::PaymentSuccess->value => 'Payment Success',
self::PaymentFailed->value => 'Payment Failed',
self::RefundIssued->value => 'Refund Issued',
self::KitchenAccept->value => 'Kitchen Accepted',
self::KitchenPreparing->value => 'Kitchen Preparing',
self::KitchenReady->value => 'Kitchen Ready',
self::KitchenServed->value => 'Kitchen Served',
self::PrintReceipt->value => 'Receipt Printed',
self::ReportGenerate->value => 'Report Generated',
self::Login->value => 'Login',
self::Logout->value => 'Logout',
self::StatusChange->value => 'Status Changed',
];
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace App\Enum;
enum OrderStatus: string
{
case Pending = 'pending';
case Confirmed = 'confirmed';
case Preparing = 'preparing';
case Ready = 'ready';
case Served = 'served';
case OutForDelivery = 'out_for_delivery';
case Completed = 'completed';
case Cancelled = 'cancelled';
case Refunded = 'refunded';
case Online = 'online';
case QrOrder = 'qr_order';
case TodayOrder = 'today_order';
// Optional: static constants for non-enum usage
public const PENDING = 'pending';
public const CONFIRMED = 'confirmed';
public const PREPARING = 'preparing';
public const READY = 'ready';
public const SERVED = 'served';
public const OUT_FOR_DELIVERY = 'out_for_delivery';
public const COMPLETED = 'completed';
public const CANCELLED = 'cancelled';
public const REFUNDED = 'refunded';
public const ONLINE = 'online';
public const QR_ORDER = 'qr_order';
public const TODAY_ORDER = 'today_order';
/**
* Get all status values as array
*/
public static function values(): array
{
return array_column(self::cases(), 'value');
}
/**
* Display labels for UI
*/
public static function labels(): array
{
return [
self::Pending->value => 'Pending',
self::Confirmed->value => 'Confirmed',
self::Preparing->value => 'Preparing',
self::Ready->value => 'Ready',
self::Served->value => 'Served',
self::OutForDelivery->value => 'Out For Delivery',
self::Completed->value => 'Completed',
self::Cancelled->value => 'Cancelled',
self::Refunded->value => 'Refunded',
self::Online->value => 'Online',
self::QrOrder->value => 'QR Order',
self::TodayOrder->value => 'Today Order',
];
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Enum;
enum OrderType: string
{
case DineIn = 'dine_in';
case Takeaway = 'takeaway';
case Delivery = 'delivery';
case DriveThru = 'drive_thru';
case Curbside = 'curbside';
// Optional: constants for non-enum usage
public const DINE_IN = 'dine_in';
public const TAKEAWAY = 'takeaway';
public const DELIVERY = 'delivery';
public const DRIVE_THRU = 'drive_thru';
public const CURBSIDE = 'curbside';
/**
* Get all order type values as array.
*/
public static function values(): array
{
return array_column(self::cases(), 'value');
}
/**
* Labels for UI.
*/
public static function labels(): array
{
return [
self::DineIn->value => 'Dine In',
self::Takeaway->value => 'Takeaway',
self::Delivery->value => 'Delivery',
self::DriveThru->value => 'Drive Thru',
self::Curbside->value => 'Curbside',
];
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Enum;
enum TrackingStatus: string
{
case Placed = 'placed';
case Accepted = 'accepted';
case Preparing = 'preparing';
case Dispatched = 'dispatched';
case Delivered = 'delivered';
case Cancelled = 'cancelled';
// Optional constants
public const PLACED = 'placed';
public const ACCEPTED = 'accepted';
public const PREPARING = 'preparing';
public const DISPATCHED = 'dispatched';
public const DELIVERED = 'delivered';
public const CANCELLED = 'cancelled';
/**
* Get all enum values
*/
public static function values(): array
{
return array_column(self::cases(), 'value');
}
/**
* UI Labels
*/
public static function labels(): array
{
return [
self::Placed->value => 'Placed',
self::Accepted->value => 'Accepted',
self::Preparing->value => 'Preparing',
self::Dispatched->value => 'Dispatched',
self::Delivered->value => 'Delivered',
self::Cancelled->value => 'Cancelled',
];
}
}

View File

@@ -0,0 +1,140 @@
<?php
namespace App\Helper;
use Carbon\Carbon;
use Exception;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\Session;
class DomainHelper
{
/**
* Get domain info from params or APP_URL, store in session.
*/
public static function getDomainInfo(?string $domain = null): array
{
$from = 'host';
if ($domain) {
$from = 'params';
} else {
$appUrl = config('app.url');
$parsedUrl = parse_url($appUrl, PHP_URL_HOST);
$domain = $parsedUrl ?: $appUrl;
}
// Check if info already in session
$sessionKey = 'domain_info_'.md5($domain);
if (Session::has($sessionKey)) {
$info = Session::get($sessionKey);
$info['from'] = $from;
return $info;
}
try {
$client = new Client(['timeout' => 10]);
$url = 'https://rdap.org/domain/'.urlencode($domain);
$res = $client->get($url);
$json = json_decode((string) $res->getBody(), true);
$registration = null;
$expiration = null;
$eventsList = [];
if (! empty($json['events']) && is_array($json['events'])) {
foreach ($json['events'] as $ev) {
if (! empty($ev['eventAction']) && ! empty($ev['eventDate'])) {
$type = strtolower($ev['eventAction']);
$date = $ev['eventDate'];
$eventsList[] = [
'type' => $type,
'date' => $date,
];
if (strpos($type, 'registration') !== false) {
$registration = $date;
}
if (strpos($type, 'expiration') !== false) {
$expiration = $date;
}
}
}
}
$info = [
'domain' => $domain,
'registration' => $registration ? Carbon::parse($registration)->format('d-F-Y h:i A') : null,
'expiration' => $expiration ? Carbon::parse($expiration)->format('d-F-Y h:i A') : null,
'events' => $eventsList,
'registrar' => $json['entities'][0]['vcardArray'][1][1][3] ?? $json['registrar'] ?? null,
'status' => $json['status'] ?? [],
'nameservers' => array_map(fn ($ns) => $ns['ldhName'] ?? null, $json['nameservers'] ?? []),
'from' => $from,
];
// Store in session
Session::put($sessionKey, $info);
return $info;
} catch (Exception $e) {
return [
'success' => false,
'message' => $e->getMessage(),
'from' => $from,
];
}
}
/**
* Check expiration warning (within 60 days, strong alert under 30 days)
*
* @param string|null $expirationDate ISO8601
* @return array ['warning' => bool, 'days_left' => int, 'level' => string, 'message' => ?string]
*/
public static function expirationWarning(?string $expirationDate): array
{
if (! $expirationDate) {
return [
'warning' => false,
'days_left' => null,
'level' => 'none',
'message' => null,
];
}
$expiration = Carbon::parse($expirationDate);
$now = Carbon::now();
// Ensure whole day difference (no fractions)
$daysLeft = (int) floor($now->diffInDays($expiration, false));
// Define thresholds
$warning = $daysLeft <= 60 && $daysLeft >= 0;
$level = 'normal';
$message = null;
if ($daysLeft < 0) {
$level = 'expired';
$message = "❌ Your domain expired on {$expiration->format('Y-m-d')}. Please renew immediately!";
} elseif ($daysLeft <= 7) {
$level = 'critical';
$message = "⚠️ Domain expiring very soon! Only {$daysLeft} days left (expires {$expiration->format('Y-m-d')}).";
} elseif ($daysLeft <= 30) {
$level = 'warning';
$message = "⚠️ Please renew your domain soon. {$daysLeft} days remaining (expires {$expiration->format('Y-m-d')}).";
} elseif ($daysLeft <= 60) {
$level = 'notice';
$message = " Your domain will expire in {$daysLeft} days on {$expiration->format('Y-m-d')}.";
}
return [
'warning' => $warning,
'days_left' => $daysLeft,
'level' => $level,
'message' => $message,
];
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Helper;
use Illuminate\Support\Facades\DB;
use Modules\Restaurant\Models\Ingredient;
use Modules\Restaurant\Models\IngredientDamage;
use Modules\Restaurant\Models\Stock;
class IngredientDamageHelper
{
public static function createDamage(array $data)
{
DB::beginTransaction();
try {
// 1⃣ Create ingredient damage record
$damage = IngredientDamage::create($data);
// 2⃣ Reduce ingredient stock
$ingredient = Ingredient::find($data['ingredient_id']);
if (! $ingredient) {
throw new \Exception('Ingredient not found.');
}
$ingredient->decrement('stock_quantity', $data['quantity']);
// 3⃣ Log stock movement
Stock::create([
'restaurant_id' => $data['restaurant_id'],
'ingredient_id' => $data['ingredient_id'],
'type' => 'damage',
'quantity' => $data['quantity'],
'unit_cost' => $data['unit_cost'] ?? null,
'total_cost' => isset($data['unit_cost']) ? $data['unit_cost'] * $data['quantity'] : null,
'reference_type' => 'IngredientDamage',
'purchase_id' => $data['purchase_id'],
'batch_no' => $data['batch_no'] ?? null,
'expiry_date' => null,
'added_by' => $data['reported_by'] ?? auth()->id(),
'remarks' => $data['reason'] ?? 'Ingredient damage',
]);
DB::commit();
return $damage;
} catch (\Exception $e) {
DB::rollBack();
throw $e;
}
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Helper;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Modules\Restaurant\Models\Order;
class OrderHelper
{
/**
* Generate a unique order number for the restaurant.
*
* Format: ORD-{YYYYMMDD}-{RestaurantId}-{AutoIncrement}
*/
public static function generateOrderNumber(int $restaurantId): string
{
$today = Carbon::now()->format('Ymd');
// Fetch last order (including soft-deleted)
$lastOrder = DB::table('orders')
->where('restaurant_id', $restaurantId)
->whereDate('created_at', Carbon::today())
->orderByDesc('id')
->first();
$lastIncrement = 0;
if ($lastOrder && preg_match('/ORD-\d{8}-\d+-([\d]+)/', $lastOrder->order_number, $matches)) {
$lastIncrement = (int) $matches[1];
}
$newIncrement = $lastIncrement + 1;
// Ensure unique, retry if needed (handles rare race conditions)
do {
$orderNumber = sprintf('ORD-%s-%d-%04d', $today, $restaurantId, $newIncrement);
$exists = Order::withTrashed()->where('order_number', $orderNumber)->exists();
if ($exists) {
$newIncrement++;
}
} while ($exists);
return $orderNumber;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Helper;
use Carbon\Carbon;
use Modules\Restaurant\Models\Purchase;
class PurchaseHelper
{
/**
* Generate a unique purchase invoice number for a restaurant.
*
* Format: PUR-{YYYYMMDD}-{RestaurantId}-{AutoIncrement}
*/
public static function generateInvoiceNumber(int $restaurantId): string
{
$today = Carbon::now()->format('Ymd');
// Get last purchase today for this restaurant
$lastPurchase = Purchase::where('restaurant_id', $restaurantId)
->whereDate('created_at', Carbon::today())
->lockForUpdate() // prevent concurrency issues
->orderBy('id', 'desc')
->first();
$lastIncrement = 0;
if ($lastPurchase && preg_match('/PUR-\d{8}-\d+-([\d]+)/', $lastPurchase->invoice_no, $matches)) {
$lastIncrement = (int) $matches[1];
}
$newIncrement = $lastIncrement + 1;
return "PUR-{$today}-{$restaurantId}-".str_pad($newIncrement, 4, '0', STR_PAD_LEFT);
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace App\Helper;
use Illuminate\Support\Facades\DB;
use Modules\Restaurant\Models\PurchaseItem;
use Modules\Restaurant\Models\PurchaseReturn;
use Modules\Restaurant\Models\Stock;
class PurchaseReturnHelper
{
/**
* Process a return for purchase items.
*
* @param array $returnItems [ ['purchase_item_id'=>1,'quantity'=>2,'reason'=>'Damaged'], ... ]
*/
public static function processReturn(int $purchaseId, array $returnItems, ?int $userId = null): array
{
$results = [];
DB::beginTransaction();
try {
foreach ($returnItems as $item) {
$purchaseItem = PurchaseItem::lockForUpdate()->findOrFail($item['purchase_item_id']);
$returnQty = (float) $item['quantity'];
$availableQty = $purchaseItem->received_quantity - $purchaseItem->returned_quantity;
if ($returnQty <= 0 || $returnQty > $availableQty) {
throw new \Exception("Invalid return quantity for ingredient ID {$purchaseItem->ingredient_id}");
}
// 1⃣ Log return in purchase_returns table
$unitCost = $purchaseItem->unit_price;
$totalCost = $unitCost * $returnQty;
$purchaseReturn = PurchaseReturn::create([
'restaurant_id' => $purchaseItem->restaurant_id,
'purchase_id' => $purchaseItem->purchase_id,
'purchase_item_id' => $purchaseItem->id,
'ingredient_id' => $purchaseItem->ingredient_id,
'quantity' => $returnQty,
'unit_cost' => $unitCost,
'total_cost' => $totalCost,
'reason' => $item['reason'] ?? 'Purchase item return',
'processed_by' => $userId,
]);
// 2⃣ Update purchase_items returned_quantity
$purchaseItem->increment('returned_quantity', $returnQty);
// 3⃣ Update stock: decrease since returned to supplier
Stock::create([
'restaurant_id' => $purchaseItem->restaurant_id,
'ingredient_id' => $purchaseItem->ingredient_id,
'type' => 'return',
'quantity' => $returnQty,
'unit_cost' => $unitCost,
'total_cost' => $totalCost,
'reference_type' => 'PurchaseReturn',
'purchase_id' => $purchaseItem->purchase_id,
'added_by' => $userId,
'remarks' => $item['reason'] ?? 'Purchase item return',
]);
// 4⃣ Update ingredient stock quantity
$purchaseItem->ingredient->decrement('stock_quantity', $returnQty);
$results[] = [
'purchase_return_id' => $purchaseReturn->id,
'purchase_item_id' => $purchaseItem->id,
'ingredient_id' => $purchaseItem->ingredient_id,
'returned_quantity' => $returnQty,
];
}
DB::commit();
return ['status' => true, 'message' => 'Return processed successfully.', 'data' => $results];
} catch (\Exception $e) {
DB::rollBack();
return ['status' => false, 'message' => $e->getMessage(), 'data' => []];
}
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace App\Helper;
use Illuminate\Support\Facades\DB;
class RestaurantHelper
{
/**
* Get restaurant ID based on the domain.
*
* @return mixed
*/
public static function getRestaurantIdByDomain(string $domain)
{
// Check if domain is provided
if (empty($domain)) {
return [
'error' => 'Domain is required.',
'status' => false,
];
}
// Check if domain format is valid (e.g., example.com)
if (! filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
return [
'error' => 'Invalid domain format.',
'status' => false,
];
}
// Fetch the restaurant based on the domain
$restaurant = DB::table('restaurants')
->where('domain', $domain)
->select('id')
->first();
// If no matching restaurant is found
if (! $restaurant) {
return [
'error' => 'Restaurant not found for the provided domain.',
'status' => false,
];
}
// Return the restaurant ID
return [
'restaurant_id' => $restaurant->id,
'status' => true,
];
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Helper;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Modules\Restaurant\Models\Ingredient;
use Modules\Restaurant\Models\Stock;
class StockHelper
{
/**
* Update ingredient stock and log the movement.
*
* @param string $type (purchase, usage, return, damage, transfer_in, transfer_out)
* @param float|null $unitCost (cost per unit)
* @param string|null $refType (e.g., Purchase, Order, Wastage)
*/
public static function updateStock(
int $ingredientId,
float $quantity,
string $type,
?float $unitCost = null,
?string $refType = null,
?int $refId = null,
?string $batchNo = null,
?string $expiryDate = null,
?int $restaurantId = null,
?int $addedBy = null
): void {
$ingredient = Ingredient::find($ingredientId);
if (! $ingredient) {
return;
}
// Determine signed quantity based on type
$signedQty = in_array($type, ['purchase', 'return', 'transfer_in', 'adjust_plus'])
? $quantity
: -$quantity;
// Update ingredient stock
$ingredient->increment('stock_quantity', $signedQty);
// Calculate total cost if unit cost is provided
$totalCost = $unitCost !== null ? $unitCost * $quantity : null;
// Record in stocks table
Stock::create([
'restaurant_id' => $restaurantId ?? $ingredient->restaurant_id ?? null,
'ingredient_id' => $ingredientId,
'type' => $type,
'quantity' => $quantity,
'unit_cost' => $unitCost,
'total_cost' => $totalCost,
'reference_type' => $refType,
'purchase_id' => $refId,
'batch_no' => $batchNo,
'expiry_date' => $expiryDate,
'added_by' => $addedBy ?? Auth::id(),
'remarks' => ucfirst($type).' stock movement',
]);
}
}

View File

@@ -0,0 +1,345 @@
<?php
use Carbon\Carbon;
use Illuminate\Mail\MailManager;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Storage;
use Modules\Authentication\Models\Setting;
use Modules\Authentication\Models\SubscriptionItem;
use Modules\Authentication\Models\User;
use Modules\HRM\Models\Attendance;
use Modules\HRM\Models\AttendanceLog;
use Modules\Restaurant\Models\FoodVariant;
if (! function_exists('_lang')) {
function _lang($string = '')
{
$targetLang = ! empty(Session::get('locale'))
? Session::get('locale')
: (! empty(get_option('language')) ? get_option('language') : 'en');
$json = [];
if (file_exists(lang_path()."/$targetLang.json")) {
$json = json_decode(file_get_contents(lang_path()."/$targetLang.json"), true);
}
return ! empty($json[$string]) ? $json[$string] : $string;
}
}
if (! function_exists('get_option')) {
function get_option($name)
{
try {
// Check if the database connection is valid
DB::connection()->getPdo();
$setting = DB::table('settings')->where('option_key', $name)->get();
if (! $setting->isEmpty()) {
return $setting[0]->option_value;
}
} catch (\Exception $e) {
// If database connection fails, return an empty string or log the error
return '';
}
}
}
if (! function_exists('get_saas_option')) {
function get_saas_option(string $name, $default = '')
{
try {
// Check DB connection
DB::connection()->getPdo();
// Ensure table exists
if (! Schema::hasTable('s_a_a_s_settings')) {
return $default;
}
// Fetch setting
$setting = DB::table('s_a_a_s_settings')
->where('name', $name)
->first();
return $setting ? $setting->value : $default;
} catch (\Throwable $e) {
return $default;
}
}
}
if (! function_exists('setEnvValue')) {
function setEnvValue(string $key, string $value): void
{
$envPath = base_path('.env');
if (! file_exists($envPath)) {
return;
}
$escapedValue = '"'.trim(str_replace('"', '\"', $value)).'"';
$envContent = file_get_contents($envPath);
if (preg_match("/^{$key}=.*/m", $envContent)) {
$envContent = preg_replace(
"/^{$key}=.*/m",
"{$key}={$escapedValue}",
$envContent
);
} else {
$envContent .= PHP_EOL."{$key}={$escapedValue}";
}
file_put_contents($envPath, $envContent);
}
}
if (! function_exists('fileUploader')) {
function fileUploader(string $dir, string $format, $image = null, $oldImage = null)
{
if ($image == null) {
return $oldImage ?? 'def.png';
}
// Delete old image(s) if exist
if (! empty($oldImage)) {
if (is_array($oldImage)) {
foreach ($oldImage as $file) {
Storage::disk('public')->delete($dir.$file);
}
} elseif (is_string($oldImage)) {
Storage::disk('public')->delete($dir.$oldImage);
}
}
// Generate unique file name
$imageName = Carbon::now()->toDateString().'-'.uniqid().'.'.$format;
// Ensure directory exists
if (! Storage::disk('public')->exists($dir)) {
Storage::disk('public')->makeDirectory($dir);
}
// Store the uploaded file
if ($image instanceof \Illuminate\Http\UploadedFile) {
$image->storeAs($dir, $imageName, 'public');
} else {
throw new \Exception('Invalid file type provided for upload.');
}
return $imageName;
}
}
if (! function_exists('fileRemover')) {
function fileRemover(string $dir, $image)
{
if (! isset($image)) {
return true;
}
if (Storage::disk('public')->exists($dir.$image)) {
Storage::disk('public')->delete($dir.$image);
}
return true;
}
}
if (! function_exists('restaurantUniqueRule')) {
function restaurantUniqueRule(string $table, string $column = 'name', ?int $restaurantId = null, ?int $ignoreId = null, string $restaurantColumn = 'restaurant_id')
{
$restaurantId = $restaurantId ?? auth()->user()->restaurant_id;
$rule = \Illuminate\Validation\Rule::unique($table, $column)
->where($restaurantColumn, $restaurantId);
if ($ignoreId) {
$rule->ignore($ignoreId);
}
return $rule;
}
}
if (! function_exists('restaurantUniqueRule')) {
function getDefaultVariant($foodId)
{
return FoodVariant::where('food_item_id', $foodId)->where('is_default', true)->first();
}
}
if (! function_exists('getUserRestaurantId')) {
function getUserRestaurantId()
{
$user = auth()->user();
if (! $user) {
return null;
}
return (int) $user->restaurant_id;
}
}
if (! function_exists('getUserId')) {
function getUserId()
{
$user = auth()->user();
if (! $user) {
return null;
}
return (int) $user->id;
}
}
if (! function_exists('authUser')) {
function authUser(): User
{
$authUser = auth()->user();
return $authUser;
}
}
if (! function_exists('updateAttendanceSummary')) {
function updateAttendanceSummary($employeeId, $date)
{
$attendance = Attendance::firstOrCreate([
'employee_id' => $employeeId,
'date' => $date,
]);
$logs = AttendanceLog::where('employee_id', $employeeId)
->whereDate('punch_time', $date)
->orderBy('punch_time')
->get();
if ($logs->isEmpty()) {
$attendance->update([
'first_clock_in' => null,
'last_clock_out' => null,
'hours_worked' => 0,
]);
return;
}
$firstIn = $logs->where('type', 'in')->first()?->punch_time;
$lastOut = $logs->where('type', 'out')->last()?->punch_time;
$totalSeconds = 0;
$stack = [];
foreach ($logs as $log) {
if ($log->type === 'in') {
$stack[] = $log->punch_time;
} elseif ($log->type === 'out' && ! empty($stack)) {
$in = array_pop($stack);
$out = $log->punch_time;
$totalSeconds += Carbon::parse($out)->diffInSeconds(Carbon::parse($in));
}
}
$hoursWorked = round($totalSeconds / 3600, 2);
$attendance->update([
'first_clock_in' => $firstIn ? Carbon::parse($firstIn)->format('H:i:s') : null,
'last_clock_out' => $lastOut ? Carbon::parse($lastOut)->format('H:i:s') : null,
'hours_worked' => $hoursWorked,
]);
}
}
if (! function_exists('checkPackageAccess')) {
function checkPackageAccess()
{
$subscription = SubscriptionItem::where(['user_id' => Auth::user()->id, 'restaurant_id' => GetUserRestaurantId()])
->with('package')->latest('id')
->first();
return $subscription->package?->name;
}
}
if (! function_exists('get_setting')) {
function get_setting($key, $restaurantId = null, $type = 'vendor', $default = null)
{
$query = Setting::query();
if ($restaurantId) {
$query->where('restaurant_id', $restaurantId);
}
if ($type) {
$query->where('type', $type);
}
$setting = $query->where('key', $key)->first();
return $setting ? $setting->value : $default;
}
}
if (! function_exists('shop_mailer_send')) {
/**
* Send email via shop SMTP dynamically
*
* @param \Illuminate\Mail\Mailable $mailable
*/
function shop_mailer_send(int $restaurantId, string $toEmail, $mailable): array
{
// 1. Check if default contact method is email
// $defaultMethod = get_setting('default_contact_method', $restaurantId);
// if ($defaultMethod !== 'email') {
// return [
// 'status' => false,
// 'message' => 'Default contact method is not email.',
// ];
// }
// 2. Get SMTP settings from database
$smtp = [
'transport' => 'smtp',
'host' => get_setting('email_smtp_host', $restaurantId),
'port' => get_setting('email_smtp_port', $restaurantId),
'encryption' => get_setting('email_smtp_encryption', $restaurantId) ?? 'tls',
'username' => get_setting('email_smtp_username', $restaurantId),
'password' => get_setting('email_smtp_password', $restaurantId),
'timeout' => null,
'local_domain' => parse_url(env('APP_URL', 'http://localhost'), PHP_URL_HOST),
];
// 3. Ensure all required credentials exist
if (empty($smtp['host']) || empty($smtp['port']) || empty($smtp['username']) || empty($smtp['password'])) {
return [
'status' => false,
'message' => 'SMTP configuration incomplete for this shop.',
];
}
try {
// 4. Dynamically set mailer config
config()->set('mail.mailers.dynamic', $smtp);
// 5. Get dynamic mailer instance
$mailer = app(MailManager::class)->mailer('dynamic');
// 6. Send email
$mailer->to($toEmail)->send($mailable);
return [
'status' => true,
'message' => 'Email sent successfully.',
];
} catch (Exception $e) {
return [
'status' => false,
'message' => 'Failed to send email: '.$e->getMessage(),
];
}
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Http\Controllers;
use App\Traits\ResponseTrait;
abstract class Controller
{
use ResponseTrait;
}

View File

@@ -0,0 +1,317 @@
<?php
namespace App\Http\Controllers\WEB;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Nwidart\Modules\Facades\Module;
use ZipArchive;
class InstallController extends Controller
{
public function index()
{
return view('install.start');
}
public function requirements()
{
$requirements = [
'PHP Version (^8.2)' => version_compare(phpversion(), '^8.2', '>='),
'OpenSSL Extension' => extension_loaded('openssl'),
'PDO Extension' => extension_loaded('PDO'),
'PDO MySQL Extension' => extension_loaded('pdo_mysql'),
'Mbstring Extension' => extension_loaded('mbstring'),
'Tokenizer Extension' => extension_loaded('tokenizer'),
'GD Extension' => extension_loaded('gd'),
'Fileinfo Extension' => extension_loaded('fileinfo'),
];
$next = true;
foreach ($requirements as $key) {
if ($key == false) {
$next = false;
}
}
return view('install.requirements', compact('requirements', 'next'));
}
public function keyWorld()
{
$next = true;
return view('install.codeniche_file', compact('next'));
}
public function step5(Request $request)
{
return view('install.database');
}
public function permissions()
{
$permissions = [
'storage/app' => is_writable(storage_path('app')),
'storage/framework/cache' => is_writable(storage_path('framework/cache')),
'storage/framework/sessions' => is_writable(storage_path('framework/sessions')),
'storage/framework/views' => is_writable(storage_path('framework/views')),
'storage/logs' => is_writable(storage_path('logs')),
'storage' => is_writable(storage_path('')),
'bootstrap/cache' => is_writable(base_path('bootstrap/cache')),
];
$next = true;
foreach ($permissions as $key) {
if ($key == false) {
$next = false;
}
}
return view('install.permissions', compact('permissions', 'next'));
}
public function database(Request $request)
{
if ($request->isMethod('post')) {
$credentials = [
'software_name' => $request->software_name,
'host' => $request->host,
'username' => $request->username,
'password' => $request->password,
'name' => $request->name,
'port' => $request->port,
];
// Step 1: Update the .env file
$this->updateEnvFile($credentials);
// Step 3: Test the Database Connection
try {
DB::purge('mysql'); // Reset connection
DB::reconnect('mysql'); // Reconnect with new settings
DB::connection()->getPdo(); // Attempt to connect
// Step 4: Check if Database Exists
if (! DB::select('SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?', [$credentials['name']])) {
return redirect()->back()->withErrors(['error' => __('The specified database does not exist.')]);
}
return redirect('install/installation');
} catch (Exception $e) {
return redirect()->back()->withErrors(['error' => 'Database connection failed: '.$e->getMessage()]);
}
}
return view('install.database');
}
private function updateEnvFile(array $credentials)
{
$path = base_path('.env');
if (file_exists($path)) {
$envContent = file_get_contents($path);
$envContent = preg_replace('/^APP_NAME=.*/m', 'APP_NAME="'.$credentials['software_name'].'"', $envContent);
$envContent = preg_replace('/^DB_HOST=.*/m', 'DB_HOST='.$credentials['host'], $envContent);
$envContent = preg_replace('/^DB_DATABASE=.*/m', 'DB_DATABASE='.$credentials['name'], $envContent);
$envContent = preg_replace('/^DB_USERNAME=.*/m', 'DB_USERNAME='.$credentials['username'], $envContent);
$envContent = preg_replace('/^DB_PASSWORD=.*/m', 'DB_PASSWORD='.$credentials['password'], $envContent);
$envContent = preg_replace('/^DB_PORT=.*/m', 'DB_PORT='.$credentials['port'], $envContent);
file_put_contents($path, $envContent);
}
}
public function installation(Request $request)
{
if ($request->isMethod('post')) {
try {
Artisan::call('view:clear');
Artisan::call('cache:clear');
Artisan::call('config:clear');
Artisan::call('migrate:fresh');
Artisan::call('db:seed');
// Seed all modules
$modules = Module::allEnabled(); // Fetch all enabled modules
foreach ($modules as $module) {
Artisan::call('module:seed', ['module' => $module->getName()]);
}
// OAuth Client for Users
$clientId1 = (string) Str::uuid();
$clientSecret1 = Str::random(40);
DB::table('oauth_clients')->insert([
'id' => $clientId1,
'owner_type' => null,
'owner_id' => null,
'name' => '1',
'secret' => Hash::make($clientSecret1),
'provider' => 'users',
'redirect_uris' => json_encode([]),
'grant_types' => json_encode(['personal_access']),
'revoked' => 0,
'created_at' => now(),
'updated_at' => now(),
]);
// OAuth Client for Customers
$clientId2 = (string) Str::uuid();
$clientSecret2 = Str::random(40);
DB::table('oauth_clients')->insert([
'id' => $clientId2,
'owner_type' => null,
'owner_id' => null,
'name' => '0',
'secret' => Hash::make($clientSecret2),
'provider' => 'customers',
'redirect_uris' => json_encode([]),
'grant_types' => json_encode(['personal_access']),
'revoked' => 0,
'created_at' => now(),
'updated_at' => now(),
]);
file_put_contents(storage_path('mightyRestaurant'), 'Welcome to MightyRestaurant Software');
// 🔥 Delete migration and seeder files
$this->deleteInstallationFiles();
return redirect('install/complete');
} catch (Exception $e) {
return redirect()->back();
}
}
return view('install.installation');
}
public function complete()
{
Artisan::call('view:clear');
Artisan::call('cache:clear');
Artisan::call('config:clear');
return view('install.complete');
}
public function validateInput(Request $request)
{
// Basic local validation for required fields
$validator = Validator::make($request->all(), [
'purchase_key' => 'required|string',
'username' => 'required|string',
]);
if ($validator->fails()) {
return response()->json([
'status' => 'error',
'errors' => $validator->errors(),
]);
}
$domain = $request->getHost(); // Get current domain
$purchaseKey = $request->purchase_key;
$userName = $request->username;
try {
$domainCheckUrl = config('install.codeniche.domain_register_check');
$response = Http::post($domainCheckUrl, [
'purchase_key' => $purchaseKey,
'domain' => $domain,
'username' => $userName,
]);
if ($response->successful()) {
return response()->json([
'status' => 'success',
'message' => $response['message'] ?? 'Domain successfully validated.',
]);
} else {
$errorMessage = $response->json()['message'] ?? 'Validation failed.';
return response()->json([
'status' => 'error',
'message' => $errorMessage,
], $response->status());
}
} catch (Exception $e) {
return response()->json([
'status' => 'error',
'message' => 'An unexpected error occurred. Please try again later.',
], 500);
}
}
/**
* Delete all migration and seeder files permanently.
*/
private function deleteInstallationFiles()
{
// Delete Laravel migrations
$migrationFiles = glob(database_path('migrations/*.php'));
foreach ($migrationFiles as $file) {
@unlink($file);
}
// Delete Laravel seeders
$seederFiles = glob(database_path('seeders/*.php'));
foreach ($seederFiles as $file) {
@unlink($file);
}
// Optional: Delete module-specific migration and seeder files
foreach (Module::all() as $module) {
$moduleMigrationPath = module_path($module->getName()).'/Database/Migrations';
$moduleSeederPath = module_path($module->getName()).'/Database/Seeders';
$moduleMigrations = glob($moduleMigrationPath.'/*.php');
foreach ($moduleMigrations as $file) {
@unlink($file);
}
$moduleSeeders = glob($moduleSeederPath.'/*.php');
foreach ($moduleSeeders as $file) {
@unlink($file);
}
}
}
public function upgradeIndex(Request $request)
{
return view('upgrade');
}
public function uploadStore(Request $request)
{
$request->validate([
'upgrade_zip' => 'required|file|mimes:zip',
]);
$file = $request->file('upgrade_zip');
$zip = new ZipArchive;
$filePath = storage_path('app/temp_upgrade.zip');
$file->move(storage_path(), 'temp_upgrade.zip');
if ($zip->open($filePath) === true) {
$zip->extractTo(base_path()); // ⚠️ this will overwrite existing files
$zip->close();
File::delete($filePath);
return back()->with('success', 'Upgrade applied successfully!');
}
return back()->with('error', 'Failed to extract zip file.');
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers\WEB;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Modules\Frontend\Models\Onboarding;
class PublicOnboardingController extends Controller
{
public function create()
{
return view('public.onboarding.create');
}
public function store(Request $request)
{
$request->validate([
'restaurant_name' => 'required|string|max:100',
'restaurant_email' => 'required|email|max:255|unique:restaurants,email',
'restaurant_phone' => 'required|string|max:15|unique:restaurants,phone',
'restaurant_domain' => 'nullable|string|max:100|unique:restaurants,domain',
'restaurant_type' => 'nullable|string|max:100',
'restaurant_address' => 'nullable|string|max:255',
'restaurant_logo' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
'name' => 'required|string|max:100',
'email' => 'required|email|max:255|unique:users,email',
'phone' => 'required|string|max:50',
'password' => 'required|string|min:4|confirmed',
'avatar' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
]);
// Handle the restaurant_logo file upload if it exists
$restaurantLogoPath = $request->hasFile('restaurant_logo')
? fileUploader('restaurants/', 'png', $request->file('restaurant_logo'))
: null;
// Handle the user_avatar file upload if it exists
$userAvatarPath = $request->hasFile('avatar')
? fileUploader('user_avatars/', 'png', $request->file('avatar'))
: null;
// Save onboarding data
Onboarding::create([
'restaurant_name' => $request->restaurant_name,
'restaurant_email' => $request->restaurant_email,
'restaurant_phone' => $request->restaurant_phone,
'restaurant_domain' => $request->restaurant_domain,
'restaurant_type' => $request->restaurant_type,
'restaurant_address' => $request->restaurant_address,
'name' => $request->name,
'email' => $request->email,
'phone' => $request->phone,
'password' => Hash::make($request->password),
'restaurant_logo' => $restaurantLogoPath,
'user_avatar' => $userAvatarPath,
'status' => 'pending',
]);
return redirect()->back()->with('success', 'Your onboarding request has been submitted successfully!');
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace App\Http\Controllers\WEB;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use PDO;
use PDOException;
class TestDbController extends Controller
{
protected $myDb;
public function testDB(Request $request)
{
$request->session()->put('env.DB_CONNECTION', $request->db_connection);
$request->session()->put('env.DB_HOST', $request->db_host);
$request->session()->put('env.DB_PORT', $request->db_port);
$request->session()->put('env.DB_DATABASE', $request->db_database);
$request->session()->put('env.DB_USERNAME', $request->db_username);
$request->session()->put('env.DB_PASSWORD', $request->db_password);
if ($request->db_connection == 'mysql') {
return $this->testMySql();
}
return response()->json([
'Error' => 'DB Type not Supported for testing',
'State' => '999',
]);
}
public function testMySql()
{
$db_type = session('env.DB_CONNECTION');
$db_host = session('env.DB_HOST');
$db_name = session('env.DB_DATABASE');
$db_user = session('env.DB_USERNAME');
$db_pass = session('env.DB_PASSWORD');
$db_port = session('env.DB_PORT');
if (! $db_name) {
return response()->json([
'Error' => 'No Database',
'State' => '999',
]);
}
if (! $db_user) {
return response()->json([
'Error' => 'No Username',
'State' => '999',
]);
}
if (! $db_port) {
return response()->json([
'Error' => 'No Port',
'State' => '999',
]);
}
if (! $db_host) {
return response()->json([
'Error' => 'No Host',
'State' => '999',
]);
}
try {
$db = new PDO($db_type.':host='.$db_host.';port='.$db_port.';dbname='.$db_name, $db_user, $db_pass, [
PDO::ATTR_TIMEOUT => '5',
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::MYSQL_ATTR_LOCAL_INFILE => true,
]);
} catch (PDOException $e) {
if ($e->getCode() == '1049') {
$db = new PDO($db_type.':host='.$db_host.';port='.$db_port.';dbname='.'', $db_user, $db_pass, [
PDO::ATTR_TIMEOUT => '5',
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::MYSQL_ATTR_LOCAL_INFILE => true,
]);
$db->query("CREATE DATABASE IF NOT EXISTS $db_name");
return response()->json([
'State' => '200',
'Success' => 'Database '.$db_name.' created',
]);
}
return response()->json([
'Error' => $e->getMessage(),
'State' => $e->getCode(),
]);
}
return response()->json([
'State' => '200',
'Success' => 'Seems okay',
]);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Http\Controllers;
use App\Services\Firebase\FirebaseService;
use Illuminate\Http\Request;
class WebsiteController extends Controller
{
public function index()
{
return view('welcome');
}
public function saveFcmToken(Request $request)
{
$request->validate(['token' => 'required|string']);
$user = auth()->user();
$user->fcm_token = $request->token;
$user->save();
return response()->json(['message' => 'FCM token saved']);
}
public function sentNotification(FirebaseService $firebase)
{
$token = auth()->user?->fcm_token;
if ($token) {
$firebase->sendNotification(
$token,
'A Push notification',
'Check the latest reply on your push notification.',
);
}
// Logic to send notification using Firebase
return response()->json(['message' => 'Notification sent']);
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Closure;
class CheckInstallation
{
public function handle($request, Closure $next)
{
if (! file_exists(storage_path('mightyRestaurant'))) {
return redirect('/install');
}
return $next($request);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class CheckInstallationStatus
{
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next)
{
if (file_exists(storage_path('mightyRestaurant'))) {
return redirect('/'); // already installed
}
return $next($request);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Middleware;
use App\Traits\ResponseTrait;
use Closure;
class CheckSubscription
{
use ResponseTrait;
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function handle($request, Closure $next)
{
$restaurant = auth()->user()->restaurant ?? null;
if (! $restaurant || ! $restaurant->activeSubscription()) {
return $this->responseError([], _lang('No active subscription.'), 403);
}
$subscription = $restaurant->activeSubscription();
$plan = $subscription->plan;
if (! $subscription->isActive()) {
return $this->responseError([], _lang('Subscription expired.'), 403);
}
return $next($request);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Http\Middleware;
use App\Traits\ResponseTrait;
use Closure;
class Customer
{
use ResponseTrait;
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
public function handle($request, Closure $next)
{
// Check if the authenticated user is not a Customer
if (auth()->user()->user_type != 'Customer') {
return $this->responseError([], _lang('You are not authorized to access this feature!'), 403);
}
return $next($request);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class DeviceApiKeyMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$apiKey = $request->header('X-DEVICE-API-KEY');
if (! $apiKey) {
return response()->json([
'status' => false,
'message' => 'API key missing',
], 401);
}
// ENV based (demo)
if ($apiKey !== config('services.device.api_key')) {
return response()->json([
'status' => false,
'message' => 'Invalid API key',
], 403);
}
return $next($request);
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* Indicates whether the XSRF-TOKEN cookie should be set on the response.
*
* @var bool
*/
protected $addHttpCookie = true;
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [
'/pay-via-ajax',
'/success',
'payment/sslcommerz/*',
'payment/paytm/pay',
'/cancel',
'/fail',
'/ipn',
'/bkash/*',
'/paytabs-response',
'/customer/choose-shipping-address',
'/system_settings',
'/paytm*',
'payment/paytabs/callback*',
];
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Symfony\Component\HttpFoundation\Response;
class VerifyTokenOrigin
{
public function handle(Request $request, Closure $next): Response
{
$requestAgent = strtolower($request->header('User-Agent'));
// Only enforce this block in production
if (! App::hasDebugModeEnabled()) {
$blockedClients = ['postman', 'curl', 'insomnia'];
foreach ($blockedClients as $client) {
if (str_contains($requestAgent, $client)) {
return response()->json(['message' => 'API clients are not allowed in production.'], 403);
}
}
// Optional: Only allow browsers
if (! str_contains($requestAgent, 'mozilla') && ! str_contains($requestAgent, 'chrome')) {
return response()->json(['message' => 'Requests must come from a browser in production.'], 403);
}
}
return $next($request);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Interfaces;
use Illuminate\Contracts\Pagination\Paginator;
interface CrudInterface
{
public function getAll(array $filterData): Paginator;
public function getCount(): int;
public function getByColumn(string $columnName, $columnValue, array $selects = ['*']): ?object;
public function getById(int $id): ?object;
public function create(array $data);
public function update(int $id, array $data): ?object;
public function delete(int $id): ?object;
}

View File

@@ -0,0 +1,8 @@
<?php
namespace App\Interfaces;
interface DBPrepareInterface
{
public function prepareForDB(array $data): array;
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Interfaces;
interface DropdownInterface
{
public function setTableName(string $tableName): self;
public function setPrimaryColumn(string $primaryColumn): self;
public function setSelectedColumns(array $selectedColumns): self;
public function getDropdowns(): array;
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Interfaces\Onboarding;
interface OnboardingInterface
{
public function index($request, int $per_page = 50);
public function getAll($request);
public function getById(int $id);
public function create(array $data);
public function update(int $id, array $data);
public function delete(int $id);
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Interfaces\Package;
interface PackageInterface
{
public function index($request, int $per_page = 50);
public function getAll($request);
public function getById(int $id);
public function create(array $data);
public function update(int $id, array $data);
public function delete(int $id);
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Interfaces\Restaurant;
interface RestaurantInterface
{
public function index($request, int $per_page = 50);
public function getAll($request);
public function getById(int $id);
public function create(array $data);
public function update(int $id, array $data);
public function delete(int $id);
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Modules\Academic\Models\SmsLog;
class SendSmsJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
// protected $smsLog;
// public function __construct(SmsLog $smsLog)
// {
// $this->smsLog = $smsLog;
// }
// public function handle()
// {
// $response = sent_sms($this->smsLog?->receiver, $this->smsLog?->message);
// if ($response) {
// $this->smsLog->status = 1;
// $this->smsLog->save();
// }
// }
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
<?php
// default responses
const GATEWAYS_DEFAULT_200 = [
'response_code' => 'gateways_default_200',
'message' => 'successfully loaded',
];
const GATEWAYS_DEFAULT_204 = [
'response_code' => 'gateways_default_204',
'message' => 'information not found',
];
const GATEWAYS_DEFAULT_400 = [
'response_code' => 'gateways_default_400',
'message' => 'invalid or missing information',
];
const GATEWAYS_DEFAULT_404 = [
'response_code' => 'gateways_default_404',
'message' => 'resource not found',
];
const GATEWAYS_DEFAULT_UPDATE_200 = [
'response_code' => 'gateways_default_update_200',
'message' => 'successfully updated',
];
const GATEWAYS_STATUS_UPDATE_FAIL = [
'response_code' => 'gateways_default_update_200',
'message' => 'status update failed',
];

View File

@@ -0,0 +1,209 @@
<?php
namespace App\Library;
use Twilio\Rest\Client;
class SMSGateway
{
public static function send($receiver, $otp): string
{
$config = self::getSettings('twilio');
if (isset($config) && $config['status'] == 1) {
return self::twilio($receiver, $otp);
}
$config = self::getSettings('nexmo');
if (isset($config) && $config['status'] == 1) {
return self::nexmo($receiver, $otp);
}
$config = self::getSettings('2factor');
if (isset($config) && $config['status'] == 1) {
return self::twoFactor($receiver, $otp);
}
$config = self::getSettings('msg91');
if (isset($config) && $config['status'] == 1) {
return self::msg91($receiver, $otp);
}
$config = self::getSettings('releans');
if (isset($config) && $config['status'] == 1) {
return self::releans($receiver, $otp);
}
return 'not_found';
}
public static function twilio($receiver, $otp): string
{
$config = self::getSettings('twilio');
$response = 'error';
if (isset($config) && $config['status'] == 1) {
$message = str_replace('#OTP#', $otp, $config['otp_template']);
$sid = $config['sid'];
$token = $config['token'];
try {
$twilio = new Client($sid, $token);
$twilio->messages
->create(
$receiver, // to
[
'messagingServiceSid' => $config['messaging_service_sid'],
'body' => $message,
]
);
$response = 'success';
} catch (\Exception $exception) {
$response = 'error';
}
}
return $response;
}
public static function nexmo($receiver, $otp): string
{
$sms_nexmo = self::getSettings('nexmo');
$response = 'error';
if (isset($sms_nexmo) && $sms_nexmo['status'] == 1) {
try {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://rest.nexmo.com/sms/json');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, 'from='.$sms_nexmo['from'].'&text='.$sms_nexmo['otp_template'].'&to='.$receiver.'&api_key='.$sms_nexmo['api_key'].'&api_secret='.$sms_nexmo['api_secret']);
$headers = [];
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$result = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Error:'.curl_error($ch);
}
curl_close($ch);
$response = 'success';
} catch (\Exception $exception) {
$response = 'error';
}
}
return $response;
}
public static function twoFactor($receiver, $otp): string
{
$config = self::getSettings('2factor');
$response = 'error';
if (isset($config) && $config['status'] == 1) {
$api_key = $config['api_key'];
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://2factor.in/API/V1/'.$api_key.'/SMS/'.$receiver.'/'.$otp.'',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'GET',
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if (! $err) {
$response = 'success';
} else {
$response = 'error';
}
}
return $response;
}
public static function msg91($receiver, $otp): string
{
$sms_nexmo = self::getSettings('nexmo');
$response = 'error';
if (isset($sms_nexmo) && $sms_nexmo['status'] == 1) {
try {
$receiver = str_replace('+', '', $receiver);
$value = 'from='.$sms_nexmo['from']
.'&text='.$sms_nexmo['otp_template'].' '.$otp.'&type=unicode'
.'&to='.$receiver
.'&api_key='.$sms_nexmo['api_key']
.'&api_secret='.$sms_nexmo['api_secret'];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://rest.nexmo.com/sms/json');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $value);
$headers = [];
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$result = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Error:'.curl_error($ch);
}
curl_close($ch);
$response = 'success';
} catch (\Exception $exception) {
$response = 'error';
}
}
return $response;
}
public static function releans($receiver, $otp): string
{
$config = self::getSettings('releans');
$response = 'error';
if (isset($config) && $config['status'] == 1) {
$curl = curl_init();
$from = $config['from'];
$to = $receiver;
$message = str_replace('#OTP#', $otp, $config['otp_template']);
try {
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.releans.com/v2/message',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => "sender=$from&mobile=$to&content=$message",
CURLOPT_HTTPHEADER => [
'Authorization: Bearer '.$config['api_key'],
],
]);
$response = curl_exec($curl);
curl_close($curl);
$response = 'success';
} catch (\Exception $exception) {
$response = 'error';
}
}
return $response;
}
public static function getSettings($name)
{
$data = null; // From settings database or config file;
if (isset($data)) {
return $data['value'];
}
return null;
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Mail;
use Illuminate\Mail\Mailable;
class BillingInvoiceMail extends Mailable
{
public function __construct(
public $shop,
public $subscription,
public $invoice,
public $paymentUrl,
public $domainInfo
) {}
public function build()
{
return $this->subject('Invoice & Subscription Expiry Notice')
->view('emails.billing.invoice');
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Modules\Restaurant\Models\Order;
class OrderInvoiceMail extends Mailable
{
use Queueable, SerializesModels;
public Order $order;
public function __construct(Order $order)
{
$this->order = $order;
}
public function build()
{
return $this->subject("Your Invoice for Order #{$this->order?->order_number}")
->markdown('emails.orders.invoice');
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class RestaurantOnboardingMail extends Mailable
{
use Queueable, SerializesModels;
public $details;
public function __construct(array $details)
{
$this->details = $details;
}
public function build()
{
return $this->subject('Welcome to '.config('app.name'))
->markdown('emails.onboarding')
->with([
'restaurantName' => $this->details['restaurant_name'],
'restaurantId' => $this->details['restaurant_id'],
'email' => $this->details['email'],
'password' => $this->details['password'],
'packageName' => $this->details['package_name'],
'trialDays' => $this->details['trial_days'],
'trialEndDate' => $this->details['trial_end_date'],
'loginUrl' => $this->details['login_url'],
]);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Modules\SupportTicket\Models\SupportTicket;
class SupportTicketCreatedMail extends Mailable
{
use Queueable, SerializesModels;
public SupportTicket $ticket;
public function __construct(SupportTicket $ticket)
{
$this->ticket = $ticket;
}
public function build()
{
return $this->subject('Support Ticket Created: '.$this->ticket->title)
->markdown('emails.support-ticket.created')
->with([
'ticket' => $this->ticket,
]);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Schedule;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
$this->app->booted(function () {
$schedule = $this->app->make(Schedule::class);
// $schedule->command('telescope:prune --hours=48')->daily();
});
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Gate;
use Laravel\Telescope\IncomingEntry;
use Laravel\Telescope\Telescope;
use Laravel\Telescope\TelescopeApplicationServiceProvider;
class TelescopeServiceProvider extends TelescopeApplicationServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// Telescope::night();
$this->hideSensitiveRequestDetails();
$isLocal = $this->app->environment('local');
Telescope::filter(function (IncomingEntry $entry) use ($isLocal) {
return $isLocal ||
$entry->isReportableException() ||
$entry->isFailedRequest() ||
$entry->isFailedJob() ||
$entry->isScheduledTask() ||
$entry->hasMonitoredTag();
});
}
/**
* Prevent sensitive request details from being logged by Telescope.
*/
protected function hideSensitiveRequestDetails(): void
{
if ($this->app->environment('local')) {
return;
}
Telescope::hideRequestParameters(['_token']);
Telescope::hideRequestHeaders([
'cookie',
'x-csrf-token',
'x-xsrf-token',
]);
}
/**
* Register the Telescope gate.
*
* This gate determines who can access Telescope in non-local environments.
*/
protected function gate(): void
{
Gate::define('viewTelescope', function ($user) {
return in_array($user->email, [
//
]);
});
}
}

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace App\Services\Firebase;
use Illuminate\Support\Facades\Log;
use Kreait\Firebase\Exception\FirebaseException;
use Kreait\Firebase\Exception\MessagingException;
use Kreait\Firebase\Factory;
use Kreait\Firebase\Messaging\CloudMessage;
use Kreait\Firebase\Messaging\Notification;
class FirebaseService
{
protected $messaging;
public function __construct()
{
$factory = (new Factory)
->withServiceAccount(storage_path('firebase/firebase_credentials.json'));
$this->messaging = $factory->createMessaging();
}
/**
* Send FCM notification to a device token
*
* @return bool|string
*/
public function sendNotification(string $token, string $title, string $body, array $data = [])
{
$message = CloudMessage::withTarget('token', $token)
->withNotification(Notification::create($title, $body))
->withData($data);
try {
$this->messaging->send($message);
return true; // Notification sent successfully
} catch (MessagingException|FirebaseException $e) {
// Log the error
Log::error('Firebase notification failed', [
'token' => $token,
'title' => $title,
'body' => $body,
'data' => $data,
'error' => $e->getMessage(),
]);
return $e->getMessage(); // Return error message
}
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace App\Services\Onboarding;
use App\Interfaces\Onboarding\OnboardingInterface;
use Modules\Frontend\Models\Onboarding;
final class OnboardingService implements OnboardingInterface
{
public function __construct(
protected Onboarding $model
) {}
/**
* Get categories by filtering args.
*/
public function get(array $args = []): \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Builder
{
$orderBy = empty($args['order_by']) ? 'id' : $args['order_by']; // column restaurant_name
$order = empty($args['order']) ? 'desc' : $args['order']; // asc, desc
$query = Onboarding::where('status', 'pending')->orderBy($orderBy, $order);
if (isset($args['is_query']) && $args['is_query']) {
return $query;
}
return $query->get();
}
public function index($request, int $per_page = 50)
{
$orderColumn = request('sort_column', 'id');
$orderDirection = request('sort_direction', 'desc');
if (! in_array($orderColumn, ['id', 'restaurant_name', 'created_at'])) {
$orderColumn = 'id';
}
if (! in_array($orderDirection, ['asc', 'desc'])) {
$orderDirection = 'desc';
}
return $this->model::query()
->when($request->search, function ($query) use ($request) {
$query->where('restaurant_name', 'like', '%'.$request->search.'%');
})
->orderBy($orderColumn, $orderDirection)
->paginate($per_page);
}
public function getAll($request)
{
$orderColumn = request('sort_column', 'id');
$orderDirection = request('sort_direction', 'desc');
if (! in_array($orderColumn, ['id', 'restaurant_name', 'created_at'])) {
$orderColumn = 'id';
}
if (! in_array($orderDirection, ['asc', 'desc'])) {
$orderDirection = 'desc';
}
return $this->model::query()
->when($request->search, function ($query) use ($request) {
$query->where('restaurant_name', 'like', $request->search.'%');
})
->orderBy($orderColumn, $orderDirection)
->get();
}
public function getById(int $id)
{
$record = $this->model::find($id);
return $record ?? null;
}
public function create(array $data)
{
return $this->model::create($data);
}
public function update(int $id, array $data)
{
$model = $this->model::findOrFail($id);
$model->update($data);
return $model;
}
public function delete(int $id)
{
$model = $this->model::findOrFail($id);
return $model->delete();
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace App\Services\Package;
use App\Interfaces\Package\PackageInterface;
use Modules\Authentication\Models\Package;
final class PackageService implements PackageInterface
{
public function __construct(
protected Package $model
) {}
/**
* Get categories by filtering args.
*/
public function get(array $args = []): \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Builder
{
$orderBy = empty($args['order_by']) ? 'id' : $args['order_by']; // column name
$order = empty($args['order']) ? 'desc' : $args['order']; // asc, desc
$query = Package::orderBy($orderBy, $order);
if (isset($args['is_query']) && $args['is_query']) {
return $query;
}
return $query->get();
}
public function index($request, int $per_page = 50)
{
$orderColumn = request('sort_column', 'id');
$orderDirection = request('sort_direction', 'desc');
if (! in_array($orderColumn, ['id', 'name', 'created_at'])) {
$orderColumn = 'id';
}
if (! in_array($orderDirection, ['asc', 'desc'])) {
$orderDirection = 'desc';
}
return $this->model::query()
->when($request->search, function ($query) use ($request) {
$query->where('name', 'like', '%'.$request->search.'%');
})
->orderBy($orderColumn, $orderDirection)
->paginate($per_page);
}
public function getAll($request)
{
$orderColumn = request('sort_column', 'id');
$orderDirection = request('sort_direction', 'desc');
if (! in_array($orderColumn, ['id', 'name', 'created_at'])) {
$orderColumn = 'id';
}
if (! in_array($orderDirection, ['asc', 'desc'])) {
$orderDirection = 'desc';
}
return $this->model::query()
->when($request->search, function ($query) use ($request) {
$query->where('name', 'like', $request->search.'%');
})
->orderBy($orderColumn, $orderDirection)
->get();
}
public function getById(int $id)
{
$record = $this->model::find($id);
return $record ?? null;
}
public function create(array $data)
{
return $this->model::create($data);
}
public function update(int $id, array $data)
{
$model = $this->model::findOrFail($id);
$model->update($data);
return $model;
}
public function delete(int $id)
{
$model = $this->model::findOrFail($id);
return $model->delete();
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace App\Services\Restaurant;
use App\Interfaces\Restaurant\RestaurantInterface;
use Modules\Authentication\Models\Restaurant;
final class RestaurantService implements RestaurantInterface
{
public function __construct(
protected Restaurant $model
) {}
/**
* Get categories by filtering args.
*/
public function get(array $args = []): \Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Eloquent\Builder
{
$orderBy = empty($args['order_by']) ? 'id' : $args['order_by']; // column name
$order = empty($args['order']) ? 'desc' : $args['order']; // asc, desc
$query = Restaurant::orderBy($orderBy, $order);
if (isset($args['is_query']) && $args['is_query']) {
return $query;
}
return $query->get();
}
public function index($request, int $per_page = 50)
{
$orderColumn = request('sort_column', 'id');
$orderDirection = request('sort_direction', 'desc');
if (! in_array($orderColumn, ['id', 'name', 'created_at'])) {
$orderColumn = 'id';
}
if (! in_array($orderDirection, ['asc', 'desc'])) {
$orderDirection = 'desc';
}
return $this->model::query()
->when($request->search, function ($query) use ($request) {
$query->where('name', 'like', '%'.$request->search.'%');
})
->orderBy($orderColumn, $orderDirection)
->paginate($per_page);
}
public function getAll($request)
{
$orderColumn = request('sort_column', 'id');
$orderDirection = request('sort_direction', 'desc');
if (! in_array($orderColumn, ['id', 'name', 'created_at'])) {
$orderColumn = 'id';
}
if (! in_array($orderDirection, ['asc', 'desc'])) {
$orderDirection = 'desc';
}
return $this->model::query()
->when($request->search, function ($query) use ($request) {
$query->where('name', 'like', $request->search.'%');
})
->orderBy($orderColumn, $orderDirection)
->get();
}
public function getById(int $id)
{
$record = $this->model::find($id);
return $record ?? null;
}
public function create(array $data)
{
return $this->model::create($data);
}
public function update(int $id, array $data)
{
$model = $this->model::findOrFail($id);
$model->update($data);
return $model;
}
public function delete(int $id)
{
$model = $this->model::findOrFail($id);
return $model->delete();
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Traits;
use Exception;
trait Authenticatable
{
/**
* @throws Exception
*/
protected function getCurrentUserId(): int
{
if (app()->runningInConsole()) {
return 1;
}
if (! isset(request()->user()->id)) {
throw new Exception('You are not authenticated to view this.');
}
return (int) request()->user()->id;
}
/**
* @throws Exception
*/
protected function getCurrentRestaurantId(): int
{
if (app()->runningInConsole()) {
return 1;
}
if (! isset(request()->user()->restaurant_id)) {
throw new Exception('You are not authenticated to view this.');
}
return (int) request()->user()->restaurant_id;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Traits;
use Illuminate\Support\Facades\Auth;
trait HasPermissionTrait
{
use ResponseTrait;
public function checkPermission($permission)
{
if (Auth::user()->hasPermissionTo($permission, 'web')) {
return true;
}
return $this->responseError([], 'You have no access on this feature', 403);
}
public function handlePermission($permission)
{
return $this->checkPermission($permission);
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace App\Traits;
use Exception;
use Illuminate\Foundation\Application;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Routing\Redirector;
use Modules\Authentication\Models\SAASSetting;
use Modules\Authentication\Models\Setting;
trait PaymentProcess
{
use Authenticatable;
public function responseFormatter($constant, $content = null, $errors = []): array
{
$constant = (array) $constant;
$constant['content'] = $content;
$constant['errors'] = $errors;
return $constant;
}
public function errorProcessor($validator): array
{
$errors = [];
foreach ($validator->errors()->getMessages() as $index => $error) {
$errors[] = ['error_code' => $index, 'message' => $error[0]];
}
return $errors;
}
public function paymentResponse($payment_info, $payment_flag): Application|JsonResponse|Redirector|RedirectResponse|\Illuminate\Contracts\Foundation\Application
{
$getNewUser = (int) 0;
$additionalData = json_decode($payment_info->additional_data, true);
if (isset($additionalData['new_customer_id']) && isset($additionalData['is_guest_in_order'])) {
$getNewUser = (int) (($additionalData['new_customer_id'] != 0 && $additionalData['is_guest_in_order'] != 1) ? 1 : 0);
}
$token_string = 'payment_method='.$payment_info->payment_method.'&&transaction_reference='.$payment_info->transaction_id;
if (in_array($payment_info->payment_platform, ['web', 'app']) && $payment_info['external_redirect_link'] != null) {
return redirect($payment_info['external_redirect_link'].'?flag='.$payment_flag.'&&token='.base64_encode($token_string).'&&new_user='.$getNewUser);
}
return redirect()->route('payment-'.$payment_flag, ['token' => base64_encode($token_string), 'new_user' => $getNewUser]);
}
public function paymentConfig($key, $settingsType, $payment): ?object
{
if (empty($key) || empty($settingsType) || empty($payment)) {
return null;
}
if ($payment->payment_type == 'subscription_payment') {
try {
$config = SAASSetting::where('name', $key)
->where('type', $settingsType)->first();
} catch (Exception $exception) {
return new SAASSetting;
}
} else {
try {
$config = Setting::where('name', $key)
->where('restaurant_id', $payment->restaurant_id)
->where('type', $settingsType)->first();
} catch (Exception $exception) {
return new Setting;
}
}
return (isset($config)) ? $config : null;
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Traits;
use Illuminate\Http\Request;
trait RequestSanitizerTrait
{
protected function getUpdateRequest(Request $request)
{
return $request->except(['_method', 'id']);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Traits;
use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
trait ResponseTrait
{
/**
* Success response.
*
* @param object|array $data
*/
public function responseSuccess($data, string $message = 'Successful'): JsonResponse
{
return response()->json([
'status' => true,
'message' => $message,
'data' => $data,
'errors' => null,
], Response::HTTP_OK);
}
/**
* Error response.
*
* @param array|object $errors
*/
public function responseError(
$errors,
string $message = 'Something went wrong.',
int $responseCode = Response::HTTP_INTERNAL_SERVER_ERROR
): JsonResponse {
return response()->json([
'status' => false,
'message' => $message,
'data' => null,
'errors' => $errors,
], $responseCode);
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace App\Traits;
use Exception;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
trait SlugAbleTrait
{
/**
* @throws Exception
*/
public function createUniqueSlug(string $title, string $tableName, string $columnName, string $separator = '-'): string
{
$id = 0;
$slug = preg_replace('/\s+/', $separator, (trim(strtolower($title))));
$slug = preg_replace('/\?+/', $separator, (trim(strtolower($slug))));
$slug = preg_replace('/\#+/', $separator, (trim(strtolower($slug))));
$slug = preg_replace('/\/+/', $separator, (trim(strtolower($slug))));
// // Replace all separator characters and whitespace by a single separator
$slug = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $slug);
$allSlugs = $this->getRelatedSlugs($slug, $tableName, $columnName, $id);
// If we haven't used it before then we are all good.
if (! $allSlugs->contains("$columnName", $slug)) {
return $slug;
}
// Just append numbers like a savage until we find not used.
for ($i = 1; $i <= 1000000000; $i++) {
$newSlug = $slug.$separator.$i;
if (! $allSlugs->contains("$columnName", $newSlug)) {
return $newSlug;
}
}
throw new Exception('Can not create a unique slug');
}
private function getRelatedSlugs(string $slug, string $tableName, string $columnName, int $id = 0): Collection
{
return DB::table($tableName)
->select("$columnName")->where("$columnName", 'like', $slug.'%')
->where('id', '<>', $id)
->get();
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Traits;
use Modules\Authentication\Models\UserLog;
trait Trackable
{
public function trackAction($action = 'create', $model = null, $model_id = null, $detail = null): void
{
try {
if (! auth()->check()) {
return;
}
$user = auth()->user();
$user_id = $user->id;
$ip_address = request()->ip();
$url = request()->fullUrl();
$userLog = new UserLog;
$userLog->user_id = $user_id;
$userLog->institute_id = getUserRestaurantId();
$userLog->user_id = $user_id;
$userLog->ip_address = $ip_address;
$userLog->action = $action;
$userLog->detail = $detail;
$userLog->model = $model;
$userLog->url = $url;
$userLog->model_id = $model_id;
$userLog->save();
} catch (\Throwable $th) {
//
}
}
}