migrate to gtea from bistbucket
This commit is contained in:
276
public/restaurant/app/Abstracts/EntityRepository.php
Normal file
276
public/restaurant/app/Abstracts/EntityRepository.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
377
public/restaurant/app/Console/Commands/MakeModuleEntity.php
Normal file
377
public/restaurant/app/Console/Commands/MakeModuleEntity.php
Normal 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!");
|
||||
}
|
||||
}
|
||||
@@ -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)'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
34
public/restaurant/app/Console/Commands/RunInvoiceJobLoop.php
Normal file
34
public/restaurant/app/Console/Commands/RunInvoiceJobLoop.php
Normal 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
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
16
public/restaurant/app/Console/Kernel.php
Normal file
16
public/restaurant/app/Console/Kernel.php
Normal 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 {
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
115
public/restaurant/app/DataTables/Package/PackageDataTable.php
Normal file
115
public/restaurant/app/DataTables/Package/PackageDataTable.php
Normal 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');
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
79
public/restaurant/app/Entity/Dropdown.php
Normal file
79
public/restaurant/app/Entity/Dropdown.php
Normal 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();
|
||||
}
|
||||
}
|
||||
10
public/restaurant/app/Entity/FilePath.php
Normal file
10
public/restaurant/app/Entity/FilePath.php
Normal 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/';
|
||||
}
|
||||
117
public/restaurant/app/Enum/ActionStatus.php
Normal file
117
public/restaurant/app/Enum/ActionStatus.php
Normal 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',
|
||||
];
|
||||
}
|
||||
}
|
||||
73
public/restaurant/app/Enum/OrderStatus.php
Normal file
73
public/restaurant/app/Enum/OrderStatus.php
Normal 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',
|
||||
];
|
||||
}
|
||||
}
|
||||
45
public/restaurant/app/Enum/OrderType.php
Normal file
45
public/restaurant/app/Enum/OrderType.php
Normal 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',
|
||||
];
|
||||
}
|
||||
}
|
||||
49
public/restaurant/app/Enum/TrackingStatus.php
Normal file
49
public/restaurant/app/Enum/TrackingStatus.php
Normal 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',
|
||||
];
|
||||
}
|
||||
}
|
||||
140
public/restaurant/app/Helper/DomainHelper.php
Normal file
140
public/restaurant/app/Helper/DomainHelper.php
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
||||
52
public/restaurant/app/Helper/IngredientDamageHelper.php
Normal file
52
public/restaurant/app/Helper/IngredientDamageHelper.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
47
public/restaurant/app/Helper/OrderHelper.php
Normal file
47
public/restaurant/app/Helper/OrderHelper.php
Normal 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;
|
||||
}
|
||||
}
|
||||
36
public/restaurant/app/Helper/PurchaseHelper.php
Normal file
36
public/restaurant/app/Helper/PurchaseHelper.php
Normal 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);
|
||||
}
|
||||
}
|
||||
86
public/restaurant/app/Helper/PurchaseReturnHelper.php
Normal file
86
public/restaurant/app/Helper/PurchaseReturnHelper.php
Normal 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' => []];
|
||||
}
|
||||
}
|
||||
}
|
||||
52
public/restaurant/app/Helper/RestaurantHelper.php
Normal file
52
public/restaurant/app/Helper/RestaurantHelper.php
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
||||
63
public/restaurant/app/Helper/StockHelper.php
Normal file
63
public/restaurant/app/Helper/StockHelper.php
Normal 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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
345
public/restaurant/app/Helper/helper.php
Normal file
345
public/restaurant/app/Helper/helper.php
Normal 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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
10
public/restaurant/app/Http/Controllers/Controller.php
Normal file
10
public/restaurant/app/Http/Controllers/Controller.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Traits\ResponseTrait;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
use ResponseTrait;
|
||||
}
|
||||
317
public/restaurant/app/Http/Controllers/WEB/InstallController.php
Normal file
317
public/restaurant/app/Http/Controllers/WEB/InstallController.php
Normal 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.');
|
||||
}
|
||||
}
|
||||
@@ -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!');
|
||||
}
|
||||
}
|
||||
104
public/restaurant/app/Http/Controllers/WEB/TestDbController.php
Normal file
104
public/restaurant/app/Http/Controllers/WEB/TestDbController.php
Normal 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',
|
||||
]);
|
||||
}
|
||||
}
|
||||
39
public/restaurant/app/Http/Controllers/WebsiteController.php
Normal file
39
public/restaurant/app/Http/Controllers/WebsiteController.php
Normal 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']);
|
||||
}
|
||||
}
|
||||
17
public/restaurant/app/Http/Middleware/CheckInstallation.php
Normal file
17
public/restaurant/app/Http/Middleware/CheckInstallation.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
35
public/restaurant/app/Http/Middleware/CheckSubscription.php
Normal file
35
public/restaurant/app/Http/Middleware/CheckSubscription.php
Normal 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);
|
||||
}
|
||||
}
|
||||
27
public/restaurant/app/Http/Middleware/Customer.php
Normal file
27
public/restaurant/app/Http/Middleware/Customer.php
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
36
public/restaurant/app/Http/Middleware/VerifyCsrfToken.php
Normal file
36
public/restaurant/app/Http/Middleware/VerifyCsrfToken.php
Normal 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*',
|
||||
];
|
||||
}
|
||||
33
public/restaurant/app/Http/Middleware/VerifyTokenOrigin.php
Normal file
33
public/restaurant/app/Http/Middleware/VerifyTokenOrigin.php
Normal 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);
|
||||
}
|
||||
}
|
||||
22
public/restaurant/app/Interfaces/CrudInterface.php
Normal file
22
public/restaurant/app/Interfaces/CrudInterface.php
Normal 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;
|
||||
}
|
||||
8
public/restaurant/app/Interfaces/DBPrepareInterface.php
Normal file
8
public/restaurant/app/Interfaces/DBPrepareInterface.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Interfaces;
|
||||
|
||||
interface DBPrepareInterface
|
||||
{
|
||||
public function prepareForDB(array $data): array;
|
||||
}
|
||||
14
public/restaurant/app/Interfaces/DropdownInterface.php
Normal file
14
public/restaurant/app/Interfaces/DropdownInterface.php
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
31
public/restaurant/app/Jobs/SendSmsJob.php
Normal file
31
public/restaurant/app/Jobs/SendSmsJob.php
Normal 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();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
1149
public/restaurant/app/Library/Constant.php
Normal file
1149
public/restaurant/app/Library/Constant.php
Normal file
File diff suppressed because it is too large
Load Diff
32
public/restaurant/app/Library/Responses.php
Normal file
32
public/restaurant/app/Library/Responses.php
Normal 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',
|
||||
];
|
||||
209
public/restaurant/app/Library/SMSGateway.php
Normal file
209
public/restaurant/app/Library/SMSGateway.php
Normal 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;
|
||||
}
|
||||
}
|
||||
22
public/restaurant/app/Mail/BillingInvoiceMail.php
Normal file
22
public/restaurant/app/Mail/BillingInvoiceMail.php
Normal 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');
|
||||
}
|
||||
}
|
||||
26
public/restaurant/app/Mail/OrderInvoiceMail.php
Normal file
26
public/restaurant/app/Mail/OrderInvoiceMail.php
Normal 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');
|
||||
}
|
||||
}
|
||||
35
public/restaurant/app/Mail/RestaurantOnboardingMail.php
Normal file
35
public/restaurant/app/Mail/RestaurantOnboardingMail.php
Normal 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'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
29
public/restaurant/app/Mail/SupportTicketCreatedMail.php
Normal file
29
public/restaurant/app/Mail/SupportTicketCreatedMail.php
Normal 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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
28
public/restaurant/app/Providers/AppServiceProvider.php
Normal file
28
public/restaurant/app/Providers/AppServiceProvider.php
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
64
public/restaurant/app/Providers/TelescopeServiceProvider.php
Normal file
64
public/restaurant/app/Providers/TelescopeServiceProvider.php
Normal 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, [
|
||||
//
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
54
public/restaurant/app/Services/Firebase/FirebaseService.php
Normal file
54
public/restaurant/app/Services/Firebase/FirebaseService.php
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
96
public/restaurant/app/Services/Package/PackageService.php
Normal file
96
public/restaurant/app/Services/Package/PackageService.php
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
40
public/restaurant/app/Traits/Authenticatable.php
Normal file
40
public/restaurant/app/Traits/Authenticatable.php
Normal 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;
|
||||
}
|
||||
}
|
||||
24
public/restaurant/app/Traits/HasPermissionTrait.php
Normal file
24
public/restaurant/app/Traits/HasPermissionTrait.php
Normal 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);
|
||||
}
|
||||
}
|
||||
78
public/restaurant/app/Traits/PaymentProcess.php
Normal file
78
public/restaurant/app/Traits/PaymentProcess.php
Normal 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;
|
||||
}
|
||||
}
|
||||
13
public/restaurant/app/Traits/RequestSanitizerTrait.php
Normal file
13
public/restaurant/app/Traits/RequestSanitizerTrait.php
Normal 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']);
|
||||
}
|
||||
}
|
||||
42
public/restaurant/app/Traits/ResponseTrait.php
Normal file
42
public/restaurant/app/Traits/ResponseTrait.php
Normal 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);
|
||||
}
|
||||
}
|
||||
48
public/restaurant/app/Traits/SlugAbleTrait.php
Normal file
48
public/restaurant/app/Traits/SlugAbleTrait.php
Normal 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();
|
||||
}
|
||||
}
|
||||
36
public/restaurant/app/Traits/Trackable.php
Normal file
36
public/restaurant/app/Traits/Trackable.php
Normal 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) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user