migrate to gtea from bistbucket

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

View File

@@ -0,0 +1,168 @@
<?php
namespace Modules\Accounting\Repositories;
use App\Abstracts\EntityRepository;
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Modules\Accounting\Models\Account;
use Symfony\Component\HttpFoundation\Response;
class AccountRepository extends EntityRepository
{
public string $table = Account::TABLE_NAME;
protected array $fillableColumns = [
'restaurant_id',
'name',
'code',
'type',
'opening_balance',
'current_balance',
'status',
];
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 getAccountQuery(): Builder
{
return $this->getQuery()
->select(
"{$this->table}.id",
"{$this->table}.restaurant_id",
"{$this->table}.name",
"{$this->table}.code",
"{$this->table}.type",
"{$this->table}.opening_balance",
"{$this->table}.current_balance",
"{$this->table}.status",
"{$this->table}.created_at",
"{$this->table}.updated_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->getAccountQuery();
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->getAccountQuery()
->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 Account::find($id);
}
/**
* @throws Exception
*/
public function update(int $id, array $data): object
{
$item = Account::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;
} else {
$data['updated_at'] = now();
}
return $data;
}
protected function getExceptionMessages(): array
{
return [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'Account does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'Account could not be deleted.',
];
}
}

View File

@@ -0,0 +1,163 @@
<?php
namespace Modules\Accounting\Repositories;
use App\Abstracts\EntityRepository;
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Modules\Accounting\Models\DepositCategory;
use Symfony\Component\HttpFoundation\Response;
class DepositCategoryRepository extends EntityRepository
{
public string $table = DepositCategory::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 getDepositCategoryQuery(): 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->getDepositCategoryQuery();
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->getDepositCategoryQuery()
->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 DepositCategory::find($id);
}
/**
* @throws Exception
*/
public function update(int $id, array $data): object
{
$item = DepositCategory::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;
} else {
$data['updated_at'] = now();
}
return $data;
}
protected function getExceptionMessages(): array
{
return [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'DepositCategory does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'DepositCategory could not be deleted.',
];
}
}

View File

@@ -0,0 +1,179 @@
<?php
namespace Modules\Accounting\Repositories;
use App\Abstracts\EntityRepository;
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Modules\Accounting\Models\Deposit;
use Symfony\Component\HttpFoundation\Response;
class DepositRepository extends EntityRepository
{
public string $table = Deposit::TABLE_NAME;
protected array $fillableColumns = [
'restaurant_id',
'fund_id',
'account_id',
'deposit_category_id',
'amount',
'transaction_date',
'voucher_no',
'received_from',
'note',
'created_by',
'status',
];
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 getDepositQuery(): Builder
{
return $this->getQuery()
->select(
"{$this->table}.id",
"{$this->table}.restaurant_id",
"{$this->table}.fund_id",
"{$this->table}.account_id",
'accounts.name as account_name',
"{$this->table}.deposit_category_id",
'deposit_categories.name as category_name',
"{$this->table}.amount",
"{$this->table}.transaction_date",
"{$this->table}.voucher_no",
"{$this->table}.received_from",
"{$this->table}.note",
"{$this->table}.created_by",
"{$this->table}.status",
"{$this->table}.created_at",
"{$this->table}.deleted_at"
)
->leftJoin('deposit_categories', 'deposit_categories.id', '=', "{$this->table}.deposit_category_id")
->leftJoin('accounts', 'accounts.id', '=', "{$this->table}.account_id");
}
protected function filterSearchQuery(Builder|EloquentBuilder $query, string $searchedText): Builder
{
$searchable = "%$searchedText%";
return $query->where("{$this->table}.transaction_date", 'LIKE', $searchable)
->orWhere("{$this->table}.status", 'LIKE', $searchable);
}
public function getAll(array $filterData = []): Paginator
{
$filter = $this->getFilterData($filterData);
$query = $this->getDepositQuery();
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->getDepositQuery()
->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 Deposit::find($id);
}
/**
* @throws Exception
*/
public function update(int $id, array $data): object
{
$item = Deposit::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;
} else {
$data['updated_at'] = now();
}
return $data;
}
protected function getExceptionMessages(): array
{
return [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'Deposit does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'Deposit could not be deleted.',
];
}
}

View File

@@ -0,0 +1,163 @@
<?php
namespace Modules\Accounting\Repositories;
use App\Abstracts\EntityRepository;
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Modules\Accounting\Models\ExpenseCategory;
use Symfony\Component\HttpFoundation\Response;
class ExpenseCategoryRepository extends EntityRepository
{
public string $table = ExpenseCategory::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 getExpenseCategoryQuery(): 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->getExpenseCategoryQuery();
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->getExpenseCategoryQuery()
->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 ExpenseCategory::find($id);
}
/**
* @throws Exception
*/
public function update(int $id, array $data): object
{
$item = ExpenseCategory::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;
} else {
$data['updated_at'] = now();
}
return $data;
}
protected function getExceptionMessages(): array
{
return [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'ExpenseCategory does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'ExpenseCategory could not be deleted.',
];
}
}

View File

@@ -0,0 +1,175 @@
<?php
namespace Modules\Accounting\Repositories;
use App\Abstracts\EntityRepository;
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Modules\Accounting\Models\Expense;
use Symfony\Component\HttpFoundation\Response;
class ExpenseRepository extends EntityRepository
{
public string $table = Expense::TABLE_NAME;
protected array $fillableColumns = [
'restaurant_id',
'fund_id',
'account_id',
'expense_category_id',
'amount',
'transaction_date',
'voucher_no',
'received_from',
'note',
'created_by',
'status',
];
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 getExpenseQuery(): Builder
{
return $this->getQuery()
->select(
"{$this->table}.id",
"{$this->table}.restaurant_id",
"{$this->table}.fund_id",
"{$this->table}.account_id",
"{$this->table}.expense_category_id",
"{$this->table}.amount",
"{$this->table}.transaction_date",
"{$this->table}.voucher_no",
"{$this->table}.received_from",
"{$this->table}.note",
"{$this->table}.created_by",
"{$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}.transaction_date", 'LIKE', $searchable)
->orWhere("{$this->table}.status", 'LIKE', $searchable);
}
public function getAll(array $filterData = []): Paginator
{
$filter = $this->getFilterData($filterData);
$query = $this->getExpenseQuery();
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->getExpenseQuery()
->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 Expense::find($id);
}
/**
* @throws Exception
*/
public function update(int $id, array $data): object
{
$item = Expense::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;
} else {
$data['updated_at'] = now();
}
return $data;
}
protected function getExceptionMessages(): array
{
return [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'Expense does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'Expense could not be deleted.',
];
}
}

View File

@@ -0,0 +1,169 @@
<?php
namespace Modules\Accounting\Repositories;
use App\Abstracts\EntityRepository;
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Modules\Accounting\Models\Fund;
use Symfony\Component\HttpFoundation\Response;
class FundRepository extends EntityRepository
{
public string $table = Fund::TABLE_NAME;
protected array $fillableColumns = [
'restaurant_id',
'name',
'type',
'code',
'opening_balance',
'current_balance',
'is_default',
'status',
];
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 getFundQuery(): Builder
{
return $this->getQuery()
->select(
"{$this->table}.id",
"{$this->table}.restaurant_id",
"{$this->table}.name",
"{$this->table}.type",
"{$this->table}.code",
"{$this->table}.opening_balance",
"{$this->table}.current_balance",
"{$this->table}.is_default",
"{$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->getFundQuery();
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->getFundQuery()
->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 Fund::find($id);
}
/**
* @throws Exception
*/
public function update(int $id, array $data): object
{
$item = Fund::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;
} else {
$data['updated_at'] = now();
}
return $data;
}
protected function getExceptionMessages(): array
{
return [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'Fund does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'Fund could not be deleted.',
];
}
}

View File

@@ -0,0 +1,163 @@
<?php
namespace Modules\Accounting\Repositories;
use App\Abstracts\EntityRepository;
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Modules\Accounting\Models\FundTransfer;
use Symfony\Component\HttpFoundation\Response;
class FundTransferRepository extends EntityRepository
{
public string $table = FundTransfer::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 getFundTransferQuery(): 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->getFundTransferQuery();
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->getFundTransferQuery()
->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 FundTransfer::find($id);
}
/**
* @throws Exception
*/
public function update(int $id, array $data): object
{
$item = FundTransfer::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;
} else {
$data['updated_at'] = now();
}
return $data;
}
protected function getExceptionMessages(): array
{
return [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'FundTransfer does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'FundTransfer could not be deleted.',
];
}
}

View File

@@ -0,0 +1,189 @@
<?php
namespace Modules\Accounting\Repositories;
use App\Abstracts\EntityRepository;
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Auth;
use Modules\Accounting\Models\TipDistribution;
use Symfony\Component\HttpFoundation\Response;
class TipDistributionRepository extends EntityRepository
{
public string $table = TipDistribution::TABLE_NAME;
protected array $fillableColumns = [
'restaurant_id',
'waiter_id',
'account_id',
'amount',
'created_by',
'distributed_at',
'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 getTipDistributionQuery(): Builder
{
return $this->getQuery()
->leftJoin('users as waiter_user', 'waiter_user.id', '=', "{$this->table}.waiter_id")
->leftJoin('accounts', 'accounts.id', '=', "{$this->table}.account_id")
->leftJoin('users as creator_user', 'creator_user.id', '=', "{$this->table}.created_by")
->select(
"{$this->table}.id",
"{$this->table}.restaurant_id",
// waiter
"{$this->table}.waiter_id",
'waiter_user.first_name as waiter_name',
// account
"{$this->table}.account_id",
'accounts.name as account_name',
// tip info
"{$this->table}.amount",
"{$this->table}.distributed_at",
// creator
"{$this->table}.created_by",
'creator_user.first_name as created_by_name',
// others
"{$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->getTipDistributionQuery();
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->getTipDistributionQuery()
->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 TipDistribution::find($id);
}
/**
* @throws Exception
*/
public function update(int $id, array $data): object
{
$item = TipDistribution::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['created_by'] = Auth::id();
$data['restaurant_id'] = $this->getCurrentRestaurantId();
$data['status'] = 1;
} else {
$data['updated_at'] = now();
}
return $data;
}
protected function getExceptionMessages(): array
{
return [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'TipDistribution does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'TipDistribution could not be deleted.',
];
}
}

View File

@@ -0,0 +1,176 @@
<?php
namespace Modules\Accounting\Repositories;
use App\Abstracts\EntityRepository;
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Modules\Accounting\Models\Tip;
use Symfony\Component\HttpFoundation\Response;
class TipRepository extends EntityRepository
{
public string $table = Tip::TABLE_NAME;
protected array $fillableColumns = [
'restaurant_id',
'waiter_id',
'order_id',
'amount',
'collected_at',
'status',
'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 getTipQuery(): Builder
{
return $this->getQuery()
->leftJoin('users', 'users.id', '=', "{$this->table}.waiter_id")
->leftJoin('orders', 'orders.id', '=', "{$this->table}.order_id")
->select(
"{$this->table}.id",
"{$this->table}.restaurant_id",
"{$this->table}.waiter_id",
'users.first_name as waiter_name',
"{$this->table}.order_id",
'orders.order_number as order_number',
"{$this->table}.amount",
"{$this->table}.collected_at",
"{$this->table}.status",
"{$this->table}.created_at",
"{$this->table}.updated_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->getTipQuery();
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->getTipQuery()
->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 Tip::find($id);
}
/**
* @throws Exception
*/
public function update(int $id, array $data): object
{
$item = Tip::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();
} else {
if (! empty($data['image']) && $data['image'] instanceof \Illuminate\Http\UploadedFile) {
$data['image'] = fileUploader('Accounting/', 'png', $data['image'], $item->image);
}
$data['updated_at'] = now();
}
return $data;
}
protected function getExceptionMessages(): array
{
return [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'Tip does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'Tip could not be deleted.',
];
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Accounting\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Accounting\Http\Requests\Account\AccountStoreRequest;
use Modules\Accounting\Http\Requests\Account\AccountUpdateRequest;
use Modules\Accounting\Repositories\AccountRepository;
class AccountController extends Controller
{
public function __construct(private AccountRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'Account has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(AccountStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'Account 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(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'Account has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(AccountUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'Account has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'Account has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Accounting\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Accounting\Http\Requests\DepositCategory\DepositCategoryStoreRequest;
use Modules\Accounting\Http\Requests\DepositCategory\DepositCategoryUpdateRequest;
use Modules\Accounting\Repositories\DepositCategoryRepository;
class DepositCategoryController extends Controller
{
public function __construct(private DepositCategoryRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'DepositCategory has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(DepositCategoryStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'DepositCategory 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(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'DepositCategory has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(DepositCategoryUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'DepositCategory has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'DepositCategory has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Accounting\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Accounting\Http\Requests\Deposit\DepositStoreRequest;
use Modules\Accounting\Http\Requests\Deposit\DepositUpdateRequest;
use Modules\Accounting\Repositories\DepositRepository;
class DepositController extends Controller
{
public function __construct(private DepositRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'Deposit has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(DepositStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'Deposit 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(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'Deposit has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(DepositUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'Deposit has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'Deposit has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Accounting\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Accounting\Http\Requests\ExpenseCategory\ExpenseCategoryStoreRequest;
use Modules\Accounting\Http\Requests\ExpenseCategory\ExpenseCategoryUpdateRequest;
use Modules\Accounting\Repositories\ExpenseCategoryRepository;
class ExpenseCategoryController extends Controller
{
public function __construct(private ExpenseCategoryRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'ExpenseCategory has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(ExpenseCategoryStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'ExpenseCategory 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(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'ExpenseCategory has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(ExpenseCategoryUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'ExpenseCategory has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'ExpenseCategory has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Accounting\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Accounting\Http\Requests\Expense\ExpenseStoreRequest;
use Modules\Accounting\Http\Requests\Expense\ExpenseUpdateRequest;
use Modules\Accounting\Repositories\ExpenseRepository;
class ExpenseController extends Controller
{
public function __construct(private ExpenseRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'Expense has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(ExpenseStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'Expense 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(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'Expense has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(ExpenseUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'Expense has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'Expense has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Accounting\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Accounting\Http\Requests\Fund\FundStoreRequest;
use Modules\Accounting\Http\Requests\Fund\FundUpdateRequest;
use Modules\Accounting\Repositories\FundRepository;
class FundController extends Controller
{
public function __construct(private FundRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'Fund has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(FundStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'Fund 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(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'Fund has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(FundUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'Fund has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'Fund has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Accounting\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Accounting\Http\Requests\FundTransfer\FundTransferStoreRequest;
use Modules\Accounting\Http\Requests\FundTransfer\FundTransferUpdateRequest;
use Modules\Accounting\Repositories\FundTransferRepository;
class FundTransferController extends Controller
{
public function __construct(private FundTransferRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'FundTransfer has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(FundTransferStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'FundTransfer 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(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'FundTransfer has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(FundTransferUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'FundTransfer has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'FundTransfer has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Accounting\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Accounting\Http\Requests\Tip\TipStoreRequest;
use Modules\Accounting\Http\Requests\Tip\TipUpdateRequest;
use Modules\Accounting\Repositories\TipRepository;
class TipController extends Controller
{
public function __construct(private TipRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'Tip has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(TipStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'Tip 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(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'Tip has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(TipUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'Tip has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'Tip has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Modules\Accounting\Http\Controllers\API;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\JsonResponse;
use Modules\Accounting\Http\Requests\TipDistribution\TipDistributionStoreRequest;
use Modules\Accounting\Http\Requests\TipDistribution\TipDistributionUpdateRequest;
use Modules\Accounting\Repositories\TipDistributionRepository;
class TipDistributionController extends Controller
{
public function __construct(private TipDistributionRepository $repo) {}
public function index(): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getAll(request()->all()), 'TipDistribution has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function store(TipDistributionStoreRequest $request): JsonResponse
{
try {
return $this->responseSuccess($this->repo->create($request->all()), 'TipDistribution 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(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->getById($id), 'TipDistribution has been fetched successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function update(TipDistributionUpdateRequest $request, int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->update($id, $request->all()), 'TipDistribution has been updated successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
public function destroy(int $id): JsonResponse
{
try {
return $this->responseSuccess($this->repo->delete($id), 'TipDistribution has been deleted successfully.');
} catch (Exception $e) {
return $this->responseError([], $e->getMessage());
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Modules\Accounting\Http\Requests\Account;
use Illuminate\Foundation\Http\FormRequest;
class AccountStoreRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'code' => ['nullable', 'string', 'max:50'],
'type' => 'required|in:asset,liability,equity,income,expense',
'opening_balance' => 'nullable|numeric|min:0',
'current_balance' => 'nullable|numeric|min:0',
'status' => 'nullable|in:0,1',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Modules\Accounting\Http\Requests\Account;
use Illuminate\Foundation\Http\FormRequest;
class AccountUpdateRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'code' => ['nullable', 'string', 'max:50'],
'type' => 'required|in:asset,liability,equity,income,expense',
'opening_balance' => 'nullable|numeric|min:0',
'current_balance' => 'nullable|numeric|min:0',
'status' => 'nullable|in:0,1',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Modules\Accounting\Http\Requests\Deposit;
use Illuminate\Foundation\Http\FormRequest;
class DepositStoreRequest extends FormRequest
{
public function rules(): array
{
return [
'fund_id' => 'required|exists:funds,id',
'account_id' => 'nullable|exists:accounts,id',
'amount' => 'required|numeric|min:0.01',
'transaction_date' => 'required|date',
'received_from' => 'nullable|string|max:255',
'note' => 'nullable|string',
'created_by' => 'nullable|exists:users,id',
'status' => 'nullable|in:0,1',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Modules\Accounting\Http\Requests\Deposit;
use Illuminate\Foundation\Http\FormRequest;
class DepositUpdateRequest extends FormRequest
{
public function rules(): array
{
$depositId = $this->route('deposit');
return [
'fund_id' => 'required|exists:funds,id',
'account_id' => 'nullable|exists:accounts,id',
'amount' => 'required|numeric|min:0.01',
'transaction_date' => 'required|date',
'received_from' => 'nullable|string|max:255',
'note' => 'nullable|string',
'created_by' => 'nullable|exists:users,id',
'status' => 'nullable|in:0,1',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Modules\Accounting\Http\Requests\DepositCategory;
use Illuminate\Foundation\Http\FormRequest;
class DepositCategoryStoreRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'status' => 'nullable|in:0,1',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Modules\Accounting\Http\Requests\DepositCategory;
use Illuminate\Foundation\Http\FormRequest;
class DepositCategoryUpdateRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'status' => 'nullable|in:0,1',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Modules\Accounting\Http\Requests\Expense;
use Illuminate\Foundation\Http\FormRequest;
class ExpenseStoreRequest extends FormRequest
{
public function rules(): array
{
return [
'account_id' => 'required|exists:accounts,id',
'expense_category_id' => 'nullable|exists:expense_categories,id',
'amount' => 'required|numeric|min:0.01',
'transaction_date' => 'required|date',
'details' => 'nullable|string',
'created_by' => 'nullable|exists:users,id',
'status' => 'nullable|in:0,1',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Modules\Accounting\Http\Requests\Expense;
use Illuminate\Foundation\Http\FormRequest;
class ExpenseUpdateRequest extends FormRequest
{
public function rules(): array
{
$expenseId = $this->route('expense');
return [
'account_id' => 'required|exists:accounts,id',
'expense_category_id' => 'nullable|exists:expense_categories,id',
'amount' => 'required|numeric|min:0.01',
'transaction_date' => 'required|date',
'details' => 'nullable|string',
'created_by' => 'nullable|exists:users,id',
'status' => 'nullable|in:0,1',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Modules\Accounting\Http\Requests\ExpenseCategory;
use Illuminate\Foundation\Http\FormRequest;
class ExpenseCategoryStoreRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'status' => 'nullable|in:0,1',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Modules\Accounting\Http\Requests\ExpenseCategory;
use Illuminate\Foundation\Http\FormRequest;
class ExpenseCategoryUpdateRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'status' => 'nullable|in:0,1',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Modules\Accounting\Http\Requests\Fund;
use Illuminate\Foundation\Http\FormRequest;
class FundStoreRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'code' => ['nullable', 'string', 'max:50'],
'type' => 'required|in:asset,liability,equity,income,expense',
'opening_balance' => 'nullable|numeric|min:0',
'current_balance' => 'nullable|numeric|min:0',
'is_default' => 'boolean',
'status' => 'nullable|in:0,1',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Modules\Accounting\Http\Requests\Fund;
use Illuminate\Foundation\Http\FormRequest;
class FundUpdateRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'code' => ['nullable', 'string', 'max:50'],
'type' => 'required|in:asset,liability,equity,income,expense',
'opening_balance' => 'nullable|numeric|min:0',
'current_balance' => 'nullable|numeric|min:0',
'is_default' => 'boolean',
'status' => 'nullable|in:0,1',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Modules\Accounting\Http\Requests\FundTransfer;
use Illuminate\Foundation\Http\FormRequest;
class FundTransferStoreRequest extends FormRequest
{
public function rules(): array
{
return [
'from_fund_id' => 'required|exists:funds,id',
'to_fund_id' => 'required|exists:funds,id|different:from_fund_id',
'amount' => 'required|numeric|min:0.01',
'transfer_date' => 'required|date',
'note' => 'nullable|string',
'created_by' => 'nullable|exists:users,id',
'status' => 'nullable|in:0,1',
];
}
public function messages(): array
{
return [
'to_fund_id.different' => 'Destination fund must be different from source fund.',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Modules\Accounting\Http\Requests\FundTransfer;
use Illuminate\Foundation\Http\FormRequest;
class FundTransferUpdateRequest extends FormRequest
{
public function rules(): array
{
$transferId = $this->route('fund_transfer');
return [
'from_fund_id' => 'required|exists:funds,id',
'to_fund_id' => 'required|exists:funds,id|different:from_fund_id',
'amount' => 'required|numeric|min:0.01',
'transfer_date' => 'required|date',
'note' => 'nullable|string',
'created_by' => 'nullable|exists:users,id',
'status' => 'nullable|in:0,1',
];
}
public function messages(): array
{
return [
'to_fund_id.different' => 'Destination fund must be different from source fund.',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Modules\Accounting\Http\Requests\Tip;
use Illuminate\Foundation\Http\FormRequest;
class TipStoreRequest extends FormRequest
{
public function rules(): array
{
return [
'waiter_id' => 'required|exists:users,id',
'order_id' => 'nullable|exists:orders,id',
'amount' => 'required|numeric|min:0.01',
'collected_at' => 'nullable|date',
'status' => 'nullable|in:0,1',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Modules\Accounting\Http\Requests\Tip;
use Illuminate\Foundation\Http\FormRequest;
class TipUpdateRequest extends FormRequest
{
public function rules(): array
{
$tipId = $this->route('tip');
return [
'waiter_id' => 'required|exists:users,id',
'order_id' => 'nullable|exists:orders,id',
'amount' => 'required|numeric|min:0.01',
'collected_at' => 'nullable|date',
'status' => 'nullable|in:0,1',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Modules\Accounting\Http\Requests\TipDistribution;
use Illuminate\Foundation\Http\FormRequest;
class TipDistributionStoreRequest extends FormRequest
{
public function rules(): array
{
return [
'waiter_id' => 'required|exists:users,id',
'account_id' => 'required|exists:accounts,id',
'amount' => 'required|numeric|min:0.01',
'distributed_at' => 'nullable|date',
'created_by' => 'nullable|exists:users,id',
'status' => 'nullable|in:0,1',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Modules\Accounting\Http\Requests\TipDistribution;
use Illuminate\Foundation\Http\FormRequest;
class TipDistributionUpdateRequest extends FormRequest
{
public function rules(): array
{
$distributionId = $this->route('tip_distribution');
return [
'waiter_id' => 'required|exists:users,id',
'account_id' => 'required|exists:accounts,id',
'amount' => 'required|numeric|min:0.01',
'distributed_at' => 'nullable|date',
'created_by' => 'nullable|exists:users,id',
'status' => 'nullable|in:0,1',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Modules\Accounting\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Account extends Model
{
protected $fillable = [
'restaurant_id',
'name',
'code',
'type',
'opening_balance',
'current_balance',
'status',
];
public const TABLE_NAME = 'accounts';
protected $table = self::TABLE_NAME;
public function transactionDetails(): HasMany
{
return $this->hasMany(TransactionDetail::class);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Modules\Accounting\Models;
use Illuminate\Database\Eloquent\Model;
class Deposit extends Model
{
protected $fillable = [
'restaurant_id',
'fund_id',
'account_id',
'deposit_category_id',
'amount',
'transaction_date',
'voucher_no',
'received_from',
'note',
'created_by',
'status',
];
protected $dates = ['transaction_date'];
public const TABLE_NAME = 'deposits';
protected $table = self::TABLE_NAME;
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Modules\Accounting\Models;
use Illuminate\Database\Eloquent\Model;
class DepositCategory extends Model
{
protected $fillable = [
'restaurant_id',
'name',
'status',
'created_at',
'updated_at',
'deleted_at',
];
public const TABLE_NAME = 'deposit_categories';
protected $table = self::TABLE_NAME;
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Modules\Accounting\Models;
use Illuminate\Database\Eloquent\Model;
class Expense extends Model
{
protected $fillable = [
'restaurant_id',
'fund_id',
'account_id',
'expense_category_id',
'amount',
'transaction_date',
'voucher_no',
'received_from',
'note',
'created_by',
'status',
];
protected $dates = ['transaction_date'];
public const TABLE_NAME = 'expenses';
protected $table = self::TABLE_NAME;
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Modules\Accounting\Models;
use Illuminate\Database\Eloquent\Model;
class ExpenseCategory extends Model
{
protected $fillable = [
'restaurant_id',
'name',
'status',
'created_at',
'updated_at',
'deleted_at',
];
public const TABLE_NAME = 'expense_categories';
protected $table = self::TABLE_NAME;
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Modules\Accounting\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Fund extends Model
{
protected $fillable = [
'restaurant_id',
'name',
'type',
'code',
'opening_balance',
'current_balance',
'is_default',
'status',
];
public const TABLE_NAME = 'funds';
protected $table = self::TABLE_NAME;
public function transactions(): HasMany
{
return $this->hasMany(Transaction::class);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Modules\Accounting\Models;
use Illuminate\Database\Eloquent\Model;
class FundTransfer extends Model
{
protected $fillable = [
'restaurant_id',
'from_fund_id',
'to_fund_id',
'amount',
'note',
'created_by',
'transfer_date',
];
protected $dates = ['transfer_date'];
public const TABLE_NAME = 'fund_transfers';
protected $table = self::TABLE_NAME;
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Modules\Accounting\Models;
use Illuminate\Database\Eloquent\Model;
class Tip extends Model
{
protected $fillable = [
'restaurant_id',
'waiter_id',
'order_id',
'amount',
'collected_at',
'status',
'created_at',
'updated_at',
'deleted_at',
];
protected $dates = ['collected_at'];
public const TABLE_NAME = 'tips';
protected $table = self::TABLE_NAME;
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Modules\Accounting\Models;
use Illuminate\Database\Eloquent\Model;
class TipDistribution extends Model
{
protected $fillable = [
'restaurant_id',
'waiter_id',
'account_id',
'amount',
'created_by',
'distributed_at',
'status',
'created_by',
'created_at',
'updated_at',
'deleted_at',
];
protected $dates = ['distributed_at'];
public const TABLE_NAME = 'tip_distributions';
protected $table = self::TABLE_NAME;
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Modules\Accounting\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Transaction extends Model
{
protected $fillable = [
'restaurant_id',
'fund_id',
'transaction_type',
'transaction_date',
'reference_no',
'notes',
'total_debit',
'total_credit',
'created_by',
];
protected $dates = ['transaction_date'];
public const TABLE_NAME = 'transactions';
protected $table = self::TABLE_NAME;
public function details(): HasMany
{
return $this->hasMany(TransactionDetail::class);
}
public function fund(): BelongsTo
{
return $this->belongsTo(Fund::class);
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Modules\Accounting\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class TransactionDetail extends Model
{
protected $fillable = [
'restaurant_id',
'transaction_id',
'account_id',
'debit',
'credit',
'note',
];
public const TABLE_NAME = 'transaction_details';
protected $table = self::TABLE_NAME;
public function account(): BelongsTo
{
return $this->belongsTo(Account::class);
}
public function transaction(): BelongsTo
{
return $this->belongsTo(Transaction::class);
}
}

View File

@@ -0,0 +1,154 @@
<?php
namespace Modules\Accounting\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\ServiceProvider;
use Nwidart\Modules\Traits\PathNamespace;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
class AccountingServiceProvider extends ServiceProvider
{
use PathNamespace;
protected string $name = 'Accounting';
protected string $nameLower = 'accounting';
/**
* Boot the application events.
*/
public function boot(): void
{
$this->registerCommands();
$this->registerCommandSchedules();
$this->registerTranslations();
$this->registerConfig();
$this->registerViews();
$this->loadMigrationsFrom(module_path($this->name, 'database/migrations'));
}
/**
* Register the service provider.
*/
public function register(): void
{
$this->app->register(EventServiceProvider::class);
$this->app->register(RouteServiceProvider::class);
}
/**
* Register commands in the format of Command::class
*/
protected function registerCommands(): void
{
// $this->commands([]);
}
/**
* Register command Schedules.
*/
protected function registerCommandSchedules(): void
{
// $this->app->booted(function () {
// $schedule = $this->app->make(Schedule::class);
// $schedule->command('inspire')->hourly();
// });
}
/**
* Register translations.
*/
public function registerTranslations(): void
{
$langPath = resource_path('lang/modules/'.$this->nameLower);
if (is_dir($langPath)) {
$this->loadTranslationsFrom($langPath, $this->nameLower);
$this->loadJsonTranslationsFrom($langPath);
} else {
$this->loadTranslationsFrom(module_path($this->name, 'lang'), $this->nameLower);
$this->loadJsonTranslationsFrom(module_path($this->name, 'lang'));
}
}
/**
* Register config.
*/
protected function registerConfig(): void
{
$configPath = module_path($this->name, config('modules.paths.generator.config.path'));
if (is_dir($configPath)) {
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($configPath));
foreach ($iterator as $file) {
if ($file->isFile() && $file->getExtension() === 'php') {
$config = str_replace($configPath.DIRECTORY_SEPARATOR, '', $file->getPathname());
$config_key = str_replace([DIRECTORY_SEPARATOR, '.php'], ['.', ''], $config);
$segments = explode('.', $this->nameLower.'.'.$config_key);
// Remove duplicated adjacent segments
$normalized = [];
foreach ($segments as $segment) {
if (end($normalized) !== $segment) {
$normalized[] = $segment;
}
}
$key = ($config === 'config.php') ? $this->nameLower : implode('.', $normalized);
$this->publishes([$file->getPathname() => config_path($config)], 'config');
$this->merge_config_from($file->getPathname(), $key);
}
}
}
}
/**
* Merge config from the given path recursively.
*/
protected function merge_config_from(string $path, string $key): void
{
$existing = config($key, []);
$module_config = require $path;
config([$key => array_replace_recursive($existing, $module_config)]);
}
/**
* Register views.
*/
public function registerViews(): void
{
$viewPath = resource_path('views/modules/'.$this->nameLower);
$sourcePath = module_path($this->name, 'resources/views');
$this->publishes([$sourcePath => $viewPath], ['views', $this->nameLower.'-module-views']);
$this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), $this->nameLower);
Blade::componentNamespace(config('modules.namespace').'\\'.$this->name.'\\View\\Components', $this->nameLower);
}
/**
* Get the services provided by the provider.
*/
public function provides(): array
{
return [];
}
private function getPublishableViewPaths(): array
{
$paths = [];
foreach (config('view.paths') as $path) {
if (is_dir($path.'/modules/'.$this->nameLower)) {
$paths[] = $path.'/modules/'.$this->nameLower;
}
}
return $paths;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Modules\Accounting\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* The event handler mappings for the application.
*
* @var array<string, array<int, string>>
*/
protected $listen = [];
/**
* Indicates if events should be discovered.
*
* @var bool
*/
protected static $shouldDiscoverEvents = true;
/**
* Configure the proper event listeners for email verification.
*/
protected function configureEmailVerification(): void {}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Modules\Accounting\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
protected string $name = 'Accounting';
/**
* Called before routes are registered.
*
* Register any model bindings or pattern based filters.
*/
public function boot(): void
{
parent::boot();
}
/**
* Define the routes for the application.
*/
public function map(): void
{
$this->mapApiRoutes();
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*/
protected function mapApiRoutes(): void
{
Route::middleware('api')->prefix('api')->name('api.')->group(module_path($this->name, '/routes/api.php'));
}
}

View File

@@ -0,0 +1,30 @@
{
"name": "nwidart/accounting",
"description": "",
"authors": [
{
"name": "Nicolas Widart",
"email": "n.widart@gmail.com"
}
],
"extra": {
"laravel": {
"providers": [],
"aliases": {
}
}
},
"autoload": {
"psr-4": {
"Modules\\Accounting\\": "app/",
"Modules\\Accounting\\Database\\Factories\\": "database/factories/",
"Modules\\Accounting\\Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Modules\\Accounting\\Tests\\": "tests/"
}
}
}

View File

@@ -0,0 +1,5 @@
<?php
return [
'name' => 'Accounting',
];

View File

@@ -0,0 +1,29 @@
<?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('accounts', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('restaurant_id')->nullable();
$table->string('name');
$table->string('code')->nullable();
$table->enum('type', ['asset', 'liability', 'equity', 'income', 'expense'])->default('asset');
$table->decimal('opening_balance', 15, 2)->default(0);
$table->decimal('current_balance', 15, 2)->default(0);
$table->smallInteger('status')->default(1)->comment('1=Active, 2=InActive');
$table->timestamps();
$table->softDeletes();
});
}
public function down(): void
{
Schema::dropIfExists('accounts');
}
};

View File

@@ -0,0 +1,30 @@
<?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('funds', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('restaurant_id')->nullable();
$table->string('name');
$table->enum('type', ['asset', 'liability', 'equity', 'income', 'expense'])->default('asset');
$table->string('code')->nullable();
$table->decimal('opening_balance', 15, 2)->default(0);
$table->decimal('current_balance', 15, 2)->default(0);
$table->boolean('is_default')->default(false);
$table->smallInteger('status')->default(1)->comment('1=Active, 2=InActive');
$table->timestamps();
$table->softDeletes();
});
}
public function down(): void
{
Schema::dropIfExists('funds');
}
};

View File

@@ -0,0 +1,32 @@
<?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('transactions', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('restaurant_id')->nullable();
$table->unsignedBigInteger('fund_id')->nullable()->index();
$table->string('transaction_type'); // deposit, payment, journal, sale, purchase, loan, transfer, tip
$table->date('transaction_date')->index();
$table->string('reference_no')->nullable()->index();
$table->text('notes')->nullable();
$table->decimal('total_debit', 15, 2)->default(0);
$table->decimal('total_credit', 15, 2)->default(0);
$table->unsignedBigInteger('created_by')->nullable();
$table->smallInteger('status')->default(1)->comment('1=Active, 2=InActive');
$table->timestamps();
$table->softDeletes();
});
}
public function down(): void
{
Schema::dropIfExists('transactions');
}
};

View File

@@ -0,0 +1,29 @@
<?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('transaction_details', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('restaurant_id')->nullable();
$table->unsignedBigInteger('transaction_id')->index();
$table->unsignedBigInteger('account_id')->index();
$table->decimal('debit', 15, 2)->default(0);
$table->decimal('credit', 15, 2)->default(0);
$table->text('note')->nullable();
$table->smallInteger('status')->default(1)->comment('1=Active, 2=InActive');
$table->timestamps();
$table->softDeletes();
});
}
public function down(): void
{
Schema::dropIfExists('transaction_details');
}
};

View File

@@ -0,0 +1,25 @@
<?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('expense_categories', 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('expense_categories');
}
};

View File

@@ -0,0 +1,33 @@
<?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('expenses', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('restaurant_id')->nullable();
$table->unsignedBigInteger('fund_id')->index(); // where money arrived
$table->unsignedBigInteger('account_id')->index(); // account used to pay
$table->unsignedBigInteger('expense_category_id')->nullable()->index();
$table->decimal('amount', 15, 2);
$table->date('transaction_date')->index();
$table->string('voucher_no')->nullable();
$table->string('received_from')->nullable();
$table->text('note')->nullable();
$table->unsignedBigInteger('created_by')->nullable();
$table->smallInteger('status')->default(1)->comment('1=Active, 2=InActive');
$table->timestamps();
$table->softDeletes();
});
}
public function down(): void
{
Schema::dropIfExists('expenses');
}
};

View File

@@ -0,0 +1,25 @@
<?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('deposit_categories', 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('deposit_categories');
}
};

View File

@@ -0,0 +1,33 @@
<?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('deposits', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('restaurant_id')->nullable();
$table->unsignedBigInteger('fund_id')->index(); // where money arrived
$table->unsignedBigInteger('account_id')->index(); // source account (if any)
$table->unsignedBigInteger('deposit_category_id')->nullable()->index();
$table->decimal('amount', 15, 2);
$table->date('transaction_date')->index();
$table->string('voucher_no')->nullable();
$table->string('received_from')->nullable();
$table->text('note')->nullable();
$table->unsignedBigInteger('created_by')->nullable();
$table->smallInteger('status')->default(1)->comment('1=Active, 2=InActive');
$table->timestamps();
$table->softDeletes();
});
}
public function down(): void
{
Schema::dropIfExists('deposits');
}
};

View File

@@ -0,0 +1,30 @@
<?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('fund_transfers', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('restaurant_id')->nullable();
$table->unsignedBigInteger('from_fund_id')->index();
$table->unsignedBigInteger('to_fund_id')->index();
$table->decimal('amount', 15, 2);
$table->text('note')->nullable();
$table->unsignedBigInteger('created_by')->nullable();
$table->date('transfer_date')->index();
$table->smallInteger('status')->default(1)->comment('1=Active, 2=InActive');
$table->timestamps();
$table->softDeletes();
});
}
public function down(): void
{
Schema::dropIfExists('fund_transfers');
}
};

View File

@@ -0,0 +1,28 @@
<?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('tips', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('restaurant_id')->nullable();
$table->unsignedBigInteger('waiter_id')->nullable()->index();
$table->unsignedBigInteger('order_id')->nullable()->index();
$table->decimal('amount', 15, 2)->default(0);
$table->date('collected_at')->nullable();
$table->smallInteger('status')->default(1)->comment('1=Active, 2=InActive');
$table->timestamps();
$table->softDeletes();
});
}
public function down(): void
{
Schema::dropIfExists('tips');
}
};

View File

@@ -0,0 +1,29 @@
<?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('tip_distributions', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('restaurant_id')->nullable();
$table->unsignedBigInteger('waiter_id')->index();
$table->unsignedBigInteger('account_id')->index();
$table->decimal('amount', 15, 2);
$table->unsignedBigInteger('created_by')->nullable();
$table->date('distributed_at')->nullable();
$table->smallInteger('status')->default(1)->comment('1=Active, 2=InActive');
$table->timestamps();
$table->softDeletes();
});
}
public function down(): void
{
Schema::dropIfExists('tip_distributions');
}
};

View File

@@ -0,0 +1,23 @@
<?php
namespace Modules\Accounting\Database\Seeders;
use Illuminate\Database\Seeder;
class AccountingDatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$this->call([
AccountingSeeder::class,
FundSeeder::class,
DepositCategorySeeder::class,
ExpenseCategorySeeder::class,
DepositSeeder::class,
ExpenseSeeder::class,
]);
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Modules\Accounting\Database\Seeders;
use Illuminate\Database\Seeder;
use Modules\Accounting\Models\Account;
class AccountingSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// In SaaS onboarding, dynamic restaurant id assigned.
// For now, making example default:
$restaurantId = 1;
/*
|--------------------------------------------------------------------------
| DEFAULT ACCOUNTS (Chart of Accounts)
|--------------------------------------------------------------------------
*/
$defaultAccounts = [
['name' => 'Cash', 'type' => 'asset', 'code' => 'AC-001'],
['name' => 'Bank', 'type' => 'asset', 'code' => 'AC-002'],
['name' => 'Loan Receivable', 'type' => 'asset', 'code' => 'AC-003'],
['name' => 'Sales', 'type' => 'income', 'code' => 'AC-101'],
['name' => 'Purchase', 'type' => 'expense', 'code' => 'AC-201'],
['name' => 'Operating Expense', 'type' => 'expense', 'code' => 'AC-202'],
['name' => 'Loan Payable', 'type' => 'liability', 'code' => 'AC-301'],
['name' => 'Tips Received', 'type' => 'liability', 'code' => 'AC-302'],
];
foreach ($defaultAccounts as $acc) {
Account::updateOrCreate(
[
'restaurant_id' => $restaurantId,
'code' => $acc['code'],
],
[
'name' => $acc['name'],
'type' => $acc['type'],
'opening_balance' => 0,
'current_balance' => 0,
'status' => 1,
]
);
}
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Modules\Accounting\Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class DepositCategorySeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
DB::table('deposit_categories')->insert([
['restaurant_id' => 1, 'name' => 'Customer Payment'],
['restaurant_id' => 1, 'name' => 'Fund Transfer In'],
['restaurant_id' => 1, 'name' => 'Sales Revenue'],
['restaurant_id' => 1, 'name' => 'Donations'],
['restaurant_id' => 1, 'name' => 'Loan Received'],
['restaurant_id' => 1, 'name' => 'Service Charge'],
]);
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace Modules\Accounting\Database\Seeders;
use Carbon\Carbon;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Modules\Accounting\Models\Account;
use Modules\Accounting\Models\Deposit;
use Modules\Accounting\Models\Fund;
use Modules\Accounting\Models\Transaction;
use Modules\Accounting\Models\TransactionDetail;
class DepositSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$restaurantId = 1; // example restaurant
// Sample Deposit Data
$deposits = [
[
'fund_name' => 'Cash', // fund where money arrived
'account_code' => 'AC-101', // source account (income/sales)
'amount' => 500.00,
'transaction_date' => Carbon::now()->subDays(2),
'voucher_no' => 'DEP-001',
'received_from' => 'Customer A',
'note' => 'Cash sale deposit',
'created_by' => 1,
],
[
'fund_name' => 'Bank',
'account_code' => 'AC-101',
'amount' => 1000.00,
'transaction_date' => Carbon::now()->subDay(),
'voucher_no' => 'DEP-002',
'received_from' => 'Customer B',
'note' => 'Bank deposit from sales',
'created_by' => 1,
],
];
foreach ($deposits as $dep) {
$fund = Fund::where('restaurant_id', $restaurantId)
->where('name', $dep['fund_name'])
->first();
$account = Account::where('restaurant_id', $restaurantId)
->where('code', $dep['account_code'])
->first();
if (! $fund || ! $account) {
continue;
}
DB::transaction(function () use ($restaurantId, $fund, $account, $dep) {
// 1⃣ Create Deposit
$deposit = Deposit::create([
'restaurant_id' => $restaurantId,
'fund_id' => $fund->id,
'account_id' => $account->id,
'deposit_category_id' => 1,
'amount' => $dep['amount'],
'transaction_date' => $dep['transaction_date'],
'voucher_no' => $dep['voucher_no'],
'received_from' => $dep['received_from'],
'note' => $dep['note'],
'created_by' => $dep['created_by'],
'status' => 1,
]);
// 2⃣ Create Transaction
$transaction = Transaction::create([
'restaurant_id' => $restaurantId,
'fund_id' => $fund->id,
'transaction_type' => 'deposit',
'transaction_date' => $dep['transaction_date'],
'reference_no' => $dep['voucher_no'],
'notes' => $dep['note'],
'total_debit' => 0,
'total_credit' => $dep['amount'],
'created_by' => $dep['created_by'],
'status' => 1,
]);
// 3⃣ Transaction Details
// Credit to Fund (increase)
TransactionDetail::create([
'restaurant_id' => $restaurantId,
'transaction_id' => $transaction->id,
'account_id' => $fund->id, // fund receives money (asset)
'debit' => $dep['amount'],
'credit' => 0,
'note' => $dep['note'].' (Fund)',
'status' => 1,
]);
// Debit from Source Account
TransactionDetail::create([
'restaurant_id' => $restaurantId,
'transaction_id' => $transaction->id,
'account_id' => $account->id, // source account (income)
'debit' => 0,
'credit' => $dep['amount'],
'note' => $dep['note'].' (Source Account)',
'status' => 1,
]);
// 4⃣ Update Fund Balance
$fund->increment('current_balance', $dep['amount']);
});
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Modules\Accounting\Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class ExpenseCategorySeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
DB::table('expense_categories')->insert([
['restaurant_id' => 1, 'name' => 'Food Purchase'],
['restaurant_id' => 1, 'name' => 'Operating Expense'],
['restaurant_id' => 1, 'name' => 'Salaries'],
['restaurant_id' => 1, 'name' => 'Marketing'],
['restaurant_id' => 1, 'name' => 'Electricity Bill'],
['restaurant_id' => 1, 'name' => 'Loan Repayment'],
['restaurant_id' => 1, 'name' => 'Fund Transfer Out'],
]);
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace Modules\Accounting\Database\Seeders;
use Carbon\Carbon;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Modules\Accounting\Models\Account;
use Modules\Accounting\Models\Expense;
use Modules\Accounting\Models\Fund;
use Modules\Accounting\Models\Transaction;
use Modules\Accounting\Models\TransactionDetail;
class ExpenseSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
$restaurantId = 1; // example restaurant
// Sample Expense Data
$expenses = [
[
'fund_name' => 'Cash', // fund where money arrived
'account_code' => 'AC-101', // source account (income/sales)
'amount' => 500.00,
'transaction_date' => Carbon::now()->subDays(2),
'voucher_no' => 'DEP-001',
'received_from' => 'Customer A',
'note' => 'Cash sale expense',
'created_by' => 1,
],
[
'fund_name' => 'Bank',
'account_code' => 'AC-101',
'amount' => 1000.00,
'transaction_date' => Carbon::now()->subDay(),
'voucher_no' => 'DEP-002',
'received_from' => 'Customer B',
'note' => 'Bank expense from sales',
'created_by' => 1,
],
];
foreach ($expenses as $dep) {
$fund = Fund::where('restaurant_id', $restaurantId)
->where('name', $dep['fund_name'])
->first();
$account = Account::where('restaurant_id', $restaurantId)
->where('code', $dep['account_code'])
->first();
if (! $fund || ! $account) {
continue;
}
DB::transaction(function () use ($restaurantId, $fund, $account, $dep) {
// 1⃣ Create Expense
$expense = Expense::create([
'restaurant_id' => $restaurantId,
'fund_id' => $fund->id,
'account_id' => $account->id,
'expense_category_id' => 1,
'amount' => $dep['amount'],
'transaction_date' => $dep['transaction_date'],
'voucher_no' => $dep['voucher_no'],
'received_from' => $dep['received_from'],
'note' => $dep['note'],
'created_by' => $dep['created_by'],
'status' => 1,
]);
// 2⃣ Create Transaction
$transaction = Transaction::create([
'restaurant_id' => $restaurantId,
'fund_id' => $fund->id,
'transaction_type' => 'expense',
'transaction_date' => $dep['transaction_date'],
'reference_no' => $dep['voucher_no'],
'notes' => $dep['note'],
'total_debit' => 0,
'total_credit' => $dep['amount'],
'created_by' => $dep['created_by'],
'status' => 1,
]);
// 3⃣ Transaction Details
// Credit to Fund (increase)
TransactionDetail::create([
'restaurant_id' => $restaurantId,
'transaction_id' => $transaction->id,
'account_id' => $fund->id, // fund receives money (asset)
'debit' => $dep['amount'],
'credit' => 0,
'note' => $dep['note'].' (Fund)',
'status' => 1,
]);
// Debit from Source Account
TransactionDetail::create([
'restaurant_id' => $restaurantId,
'transaction_id' => $transaction->id,
'account_id' => $account->id, // source account (income)
'debit' => 0,
'credit' => $dep['amount'],
'note' => $dep['note'].' (Source Account)',
'status' => 1,
]);
// 4⃣ Update Fund Balance
$fund->increment('current_balance', $dep['amount']);
});
}
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Modules\Accounting\Database\Seeders;
use Illuminate\Database\Seeder;
use Modules\Accounting\Models\Fund;
class FundSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// In SaaS onboarding, dynamic restaurant id assigned.
// For now, making example default:
$restaurantId = 1;
/*
|--------------------------------------------------------------------------
| DEFAULT FUNDS (Cash Sources)
|--------------------------------------------------------------------------
*/
$defaultFunds = [
['name' => 'Cash', 'type' => 'asset', 'code' => 'FD-001', 'is_default' => true],
['name' => 'Bank', 'type' => 'asset', 'code' => 'FD-002', 'is_default' => false],
['name' => 'Wallet', 'type' => 'asset', 'code' => 'FD-003', 'is_default' => false],
];
foreach ($defaultFunds as $fund) {
Fund::updateOrCreate(
[
'restaurant_id' => $restaurantId,
'code' => $fund['code'],
],
[
'name' => $fund['name'],
'type' => $fund['type'],
'opening_balance' => 0,
'current_balance' => 0,
'is_default' => $fund['is_default'],
'status' => 1,
]
);
}
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "Accounting",
"alias": "accounting",
"description": "",
"keywords": [],
"priority": 0,
"providers": [
"Modules\\Accounting\\Providers\\AccountingServiceProvider"
],
"files": []
}

View File

@@ -0,0 +1,15 @@
{
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build"
},
"devDependencies": {
"axios": "^1.1.2",
"laravel-vite-plugin": "^0.7.5",
"sass": "^1.69.5",
"postcss": "^8.3.7",
"vite": "^4.0.0"
}
}

View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Accounting Module - {{ config('app.name', 'Laravel') }}</title>
<meta name="description" content="{{ $description ?? '' }}">
<meta name="keywords" content="{{ $keywords ?? '' }}">
<meta name="author" content="{{ $author ?? '' }}">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
{{-- Vite CSS --}}
{{-- {{ module_vite('build-accounting', 'resources/assets/sass/app.scss') }} --}}
</head>
<body>
{{ $slot }}
{{-- Vite JS --}}
{{-- {{ module_vite('build-accounting', 'resources/assets/js/app.js') }} --}}
</body>
</html>

View File

@@ -0,0 +1,5 @@
<x-accounting::layouts.master>
<h1>Hello World</h1>
<p>Module: {!! config('accounting.name') !!}</p>
</x-accounting::layouts.master>

View File

@@ -0,0 +1,27 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\Accounting\Http\Controllers\API\AccountController;
use Modules\Accounting\Http\Controllers\API\DepositCategoryController;
use Modules\Accounting\Http\Controllers\API\DepositController;
use Modules\Accounting\Http\Controllers\API\ExpenseCategoryController;
use Modules\Accounting\Http\Controllers\API\ExpenseController;
use Modules\Accounting\Http\Controllers\API\FundController;
use Modules\Accounting\Http\Controllers\API\FundTransferController;
use Modules\Accounting\Http\Controllers\API\TipController;
use Modules\Accounting\Http\Controllers\API\TipDistributionController;
Route::prefix('/v1')->group(function () {
// Protected Routes (Requires Authentication)
Route::middleware(['auth:api'])->group(function () {
Route::apiResource('accounts', AccountController::class);
Route::apiResource('funds', FundController::class);
Route::apiResource('deposit-categories', DepositCategoryController::class);
Route::apiResource('deposits', DepositController::class);
Route::apiResource('expense-categories', ExpenseCategoryController::class);
Route::apiResource('expenses', ExpenseController::class);
Route::apiResource('fund-transfers', FundTransferController::class);
Route::apiResource('tips', TipController::class);
Route::apiResource('tip-distributions', TipDistributionController::class);
});
});

View File

@@ -0,0 +1,57 @@
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import { readdirSync, statSync } from 'fs';
import { join,relative,dirname } from 'path';
import { fileURLToPath } from 'url';
export default defineConfig({
build: {
outDir: '../../public/build-accounting',
emptyOutDir: true,
manifest: true,
},
plugins: [
laravel({
publicDirectory: '../../public',
buildDirectory: 'build-accounting',
input: [
__dirname + '/resources/assets/sass/app.scss',
__dirname + '/resources/assets/js/app.js'
],
refresh: true,
}),
],
});
// Scen all resources for assets file. Return array
//function getFilePaths(dir) {
// const filePaths = [];
//
// function walkDirectory(currentPath) {
// const files = readdirSync(currentPath);
// for (const file of files) {
// const filePath = join(currentPath, file);
// const stats = statSync(filePath);
// if (stats.isFile() && !file.startsWith('.')) {
// const relativePath = 'Modules/Accounting/'+relative(__dirname, filePath);
// filePaths.push(relativePath);
// } else if (stats.isDirectory()) {
// walkDirectory(filePath);
// }
// }
// }
//
// walkDirectory(dir);
// return filePaths;
//}
//const __filename = fileURLToPath(import.meta.url);
//const __dirname = dirname(__filename);
//const assetsDir = join(__dirname, 'resources/assets');
//export const paths = getFilePaths(assetsDir);
//export const paths = [
// 'Modules/Accounting/resources/assets/sass/app.scss',
// 'Modules/Accounting/resources/assets/js/app.js',
//];

View File

@@ -0,0 +1,16 @@
<?php
namespace Modules\Authentication\Interfaces;
interface PermissionInterface
{
public function index($request, int $perPage = 50);
public function getById(int $id);
public function create(array $data);
public function update(int $id, array $data);
public function delete(int $id);
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Modules\Authentication\Interfaces;
interface RoleInterface
{
public function index($request, int $perPage = 50);
public function getById(int $id);
public function create(array $data);
public function update(int $id, array $data);
public function delete(int $id);
}

View File

@@ -0,0 +1,173 @@
<?php
namespace Modules\Authentication\Repositories;
use App\Abstracts\EntityRepository;
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Modules\Authentication\Models\Feedback;
use Symfony\Component\HttpFoundation\Response;
class FeedbackRepository extends EntityRepository
{
public string $table = Feedback::TABLE_NAME;
protected array $fillableColumns = [
'user_id',
'description',
'status',
'created_at',
'updated_at',
];
protected function getQuery(): Builder
{
return parent::getQuery();
}
public function getAll(array $filterData = []): Paginator
{
$filter = $this->getFilterData($filterData);
$query = $this->getFeedbackQuery();
if (! $filter['with_deleted']) {
$query->whereNull("{$this->table}.deleted_at");
}
if (isset($filter['search']) && strlen($filter['search']) > 0) {
$query = $this->filterSearchQuery($query, $filter['search']);
}
return $query
->orderBy($filter['orderBy'], $filter['order'])
->paginate($filter['perPage']);
}
protected function getFilterData(array $filterData = []): array
{
$defaultArgs = [
'perPage' => 10,
'search' => '',
'orderBy' => 'id',
'order' => 'desc',
'with_deleted' => false,
];
return array_merge($defaultArgs, $filterData);
}
private function getFeedbackQuery(): Builder
{
return $this->getQuery()
->select(
'feedback.id',
'feedback.user_id',
'feedback.description',
'feedback.status',
'feedback.created_at'
);
}
protected function filterSearchQuery(Builder|EloquentBuilder $query, string $searchedText): Builder
{
$searchable = "%$searchedText%";
return $query->where('feedback.user_id', 'LIKE', $searchable)
->orWhere('feedback.description', 'LIKE', $searchable)
->orWhere('feedback.status', 'LIKE', $searchable);
}
/**
* @throws Exception
*/
public function getByColumn(string $columnName, $columnValue, array $selects = ['*']): ?object
{
$user = $this->getFeedbackQuery()
->where($columnName, $columnValue)
->first();
if (empty($user)) {
throw new Exception(
$this->getExceptionMessage(static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE),
Response::HTTP_NOT_FOUND
);
}
return $user;
}
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 create(array $data): object
{
try {
$data = $this->prepareForDB($data);
$userId = $this->getQuery()->insertGetId($data);
$user = Feedback::find($userId);
return $user;
} catch (Exception $exception) {
throw new Exception($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* @throws Exception
*/
public function prepareForDB(array $data, ?object $item = null): array
{
$data = parent::prepareForDB($data, $item);
if (empty($item)) {
$data['created_at'] = now();
$data['status'] = 1;
} else {
$data['updated_at'] = now();
}
return $data;
}
/**
* @throws Exception
*/
public function update(int $id, array $data): ?object
{
try {
$user = Feedback::find($id);
$data = $this->prepareForDB($data, $user);
parent::update($id, $data);
return $this->getById($id);
} catch (Exception $exception) {
throw new Exception($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
protected function getExceptionMessages(): array
{
$exceptionMessages = parent::getExceptionMessages();
$userExceptionMessages = [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'Feedback does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'Feedback could not be deleted.',
];
return array_merge($exceptionMessages, $userExceptionMessages);
}
}

View File

@@ -0,0 +1,207 @@
<?php
namespace Modules\Authentication\Repositories;
use App\Abstracts\EntityRepository;
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Modules\Authentication\Models\RestaurantImageSAASSetting;
use Symfony\Component\HttpFoundation\Response;
class RestaurantImageSAASSettingRepository extends EntityRepository
{
public string $table = RestaurantImageSAASSetting::TABLE_NAME;
protected array $fillableColumns = [
'header_logo_light_theme',
'header_logo_dark_theme',
'footer_logo_light_theme',
'footer_logo_dark_theme',
'banner_image',
'status',
'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 getRestaurantImageSAASSettingQuery(): Builder
{
return $this->getQuery()
->select(
"{$this->table}.id",
"{$this->table}.header_logo_light_theme",
"{$this->table}.header_logo_dark_theme",
"{$this->table}.footer_logo_light_theme",
"{$this->table}.footer_logo_dark_theme",
"{$this->table}.banner_image",
"{$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->getRestaurantImageSAASSettingQuery();
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->getRestaurantImageSAASSettingQuery()
->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 RestaurantImageSAASSetting::find($id);
}
/**
* @throws Exception
*/
public function update(int $id, array $data): object
{
$item = RestaurantImageSAASSetting::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['status'] = 1;
if (! empty($data['header_logo_light_theme']) && $data['header_logo_light_theme'] instanceof \Illuminate\Http\UploadedFile) {
$data['header_logo_light_theme'] = fileUploader('restaurant_image_settings/', 'png', $data['header_logo_light_theme']);
}
if (! empty($data['header_logo_dark_theme']) && $data['header_logo_dark_theme'] instanceof \Illuminate\Http\UploadedFile) {
$data['header_logo_dark_theme'] = fileUploader('restaurant_image_settings/', 'png', $data['header_logo_dark_theme']);
}
if (! empty($data['footer_logo_light_theme']) && $data['footer_logo_light_theme'] instanceof \Illuminate\Http\UploadedFile) {
$data['footer_logo_light_theme'] = fileUploader('restaurant_image_settings/', 'png', $data['footer_logo_light_theme']);
}
if (! empty($data['footer_logo_dark_theme']) && $data['footer_logo_dark_theme'] instanceof \Illuminate\Http\UploadedFile) {
$data['footer_logo_dark_theme'] = fileUploader('restaurant_image_settings/', 'png', $data['footer_logo_dark_theme']);
}
if (! empty($data['banner_image']) && $data['banner_image'] instanceof \Illuminate\Http\UploadedFile) {
$data['banner_image'] = fileUploader('restaurant_image_settings/', 'png', $data['banner_image']);
}
} else {
if (! empty($data['header_logo_light_theme']) && $data['header_logo_light_theme'] instanceof \Illuminate\Http\UploadedFile) {
$data['header_logo_light_theme'] = fileUploader('restaurant_image_settings/', 'png', $data['header_logo_light_theme'], $item->header_logo_light_theme);
}
if (! empty($data['header_logo_dark_theme']) && $data['header_logo_dark_theme'] instanceof \Illuminate\Http\UploadedFile) {
$data['header_logo_dark_theme'] = fileUploader('restaurant_image_settings/', 'png', $data['header_logo_dark_theme'], $item->header_logo_dark_theme);
}
if (! empty($data['footer_logo_light_theme']) && $data['footer_logo_light_theme'] instanceof \Illuminate\Http\UploadedFile) {
$data['footer_logo_light_theme'] = fileUploader('restaurant_image_settings/', 'png', $data['footer_logo_light_theme'], $item->footer_logo_light_theme);
}
if (! empty($data['footer_logo_dark_theme']) && $data['footer_logo_dark_theme'] instanceof \Illuminate\Http\UploadedFile) {
$data['footer_logo_dark_theme'] = fileUploader('restaurant_image_settings/', 'png', $data['footer_logo_dark_theme'], $item->footer_logo_dark_theme);
}
if (! empty($data['banner_image']) && $data['banner_image'] instanceof \Illuminate\Http\UploadedFile) {
$data['banner_image'] = fileUploader('restaurant_image_settings/', 'png', $data['banner_image'], $item->banner_image);
}
$data['updated_at'] = now();
}
return $data;
}
protected function getExceptionMessages(): array
{
return [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'RestaurantImageSAASSetting does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'RestaurantImageSAASSetting could not be deleted.',
];
}
}

View File

@@ -0,0 +1,210 @@
<?php
namespace Modules\Authentication\Repositories;
use App\Abstracts\EntityRepository;
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Modules\Authentication\Models\RestaurantImageSetting;
use Symfony\Component\HttpFoundation\Response;
class RestaurantImageSettingRepository extends EntityRepository
{
public string $table = RestaurantImageSetting::TABLE_NAME;
protected array $fillableColumns = [
'restaurant_id',
'header_logo_light_theme',
'header_logo_dark_theme',
'footer_logo_light_theme',
'footer_logo_dark_theme',
'banner_image',
'status',
'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 getRestaurantImageSettingQuery(): Builder
{
return $this->getQuery()
->select(
"{$this->table}.id",
"{$this->table}.restaurant_id",
"{$this->table}.header_logo_light_theme",
"{$this->table}.header_logo_dark_theme",
"{$this->table}.footer_logo_light_theme",
"{$this->table}.footer_logo_dark_theme",
"{$this->table}.banner_image",
"{$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->getRestaurantImageSettingQuery();
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->getRestaurantImageSettingQuery()
->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 RestaurantImageSetting::find($id);
}
/**
* @throws Exception
*/
public function update(int $id, array $data): object
{
$item = RestaurantImageSetting::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['header_logo_light_theme']) && $data['header_logo_light_theme'] instanceof \Illuminate\Http\UploadedFile) {
$data['header_logo_light_theme'] = fileUploader('restaurant_image_settings/', 'png', $data['header_logo_light_theme']);
}
if (! empty($data['header_logo_dark_theme']) && $data['header_logo_dark_theme'] instanceof \Illuminate\Http\UploadedFile) {
$data['header_logo_dark_theme'] = fileUploader('restaurant_image_settings/', 'png', $data['header_logo_dark_theme']);
}
if (! empty($data['footer_logo_light_theme']) && $data['footer_logo_light_theme'] instanceof \Illuminate\Http\UploadedFile) {
$data['footer_logo_light_theme'] = fileUploader('restaurant_image_settings/', 'png', $data['footer_logo_light_theme']);
}
if (! empty($data['footer_logo_dark_theme']) && $data['footer_logo_dark_theme'] instanceof \Illuminate\Http\UploadedFile) {
$data['footer_logo_dark_theme'] = fileUploader('restaurant_image_settings/', 'png', $data['footer_logo_dark_theme']);
}
if (! empty($data['banner_image']) && $data['banner_image'] instanceof \Illuminate\Http\UploadedFile) {
$data['banner_image'] = fileUploader('restaurant_image_settings/', 'png', $data['banner_image']);
}
} else {
if (! empty($data['header_logo_light_theme']) && $data['header_logo_light_theme'] instanceof \Illuminate\Http\UploadedFile) {
$data['header_logo_light_theme'] = fileUploader('restaurant_image_settings/', 'png', $data['header_logo_light_theme'], $item->header_logo_light_theme);
}
if (! empty($data['header_logo_dark_theme']) && $data['header_logo_dark_theme'] instanceof \Illuminate\Http\UploadedFile) {
$data['header_logo_dark_theme'] = fileUploader('restaurant_image_settings/', 'png', $data['header_logo_dark_theme'], $item->header_logo_dark_theme);
}
if (! empty($data['footer_logo_light_theme']) && $data['footer_logo_light_theme'] instanceof \Illuminate\Http\UploadedFile) {
$data['footer_logo_light_theme'] = fileUploader('restaurant_image_settings/', 'png', $data['footer_logo_light_theme'], $item->footer_logo_light_theme);
}
if (! empty($data['footer_logo_dark_theme']) && $data['footer_logo_dark_theme'] instanceof \Illuminate\Http\UploadedFile) {
$data['footer_logo_dark_theme'] = fileUploader('restaurant_image_settings/', 'png', $data['footer_logo_dark_theme'], $item->footer_logo_dark_theme);
}
if (! empty($data['banner_image']) && $data['banner_image'] instanceof \Illuminate\Http\UploadedFile) {
$data['banner_image'] = fileUploader('restaurant_image_settings/', 'png', $data['banner_image'], $item->banner_image);
}
$data['updated_at'] = now();
}
return $data;
}
protected function getExceptionMessages(): array
{
return [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'RestaurantImageSetting does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'RestaurantImageSetting could not be deleted.',
];
}
}

View File

@@ -0,0 +1,237 @@
<?php
namespace Modules\Authentication\Repositories;
use App\Abstracts\EntityRepository;
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Modules\Authentication\Models\Restaurant;
use Modules\Authentication\Services\RestaurantSetupService;
use Symfony\Component\HttpFoundation\Response;
class RestaurantRepository extends EntityRepository
{
public string $table = Restaurant::TABLE_NAME;
protected array $fillableColumns = [
'owner_id',
'assigned_to',
'name',
'email',
'address',
'restaurant_type',
'phone',
'domain',
'platform',
'last_active_time',
'logo',
'status',
'theme_id',
'created_at',
'updated_at',
'deleted_at',
];
protected function getQuery(): Builder
{
return parent::getQuery();
}
public function getAll(array $filterData = []): Paginator
{
$filter = $this->getFilterData($filterData);
$query = $this->getRestaurantQuery();
if (! $filter['with_deleted']) {
$query->whereNull("{$this->table}.deleted_at");
}
if (isset($filter['search']) && strlen($filter['search']) > 0) {
$query = $this->filterSearchQuery($query, $filter['search']);
}
return $query
->orderBy($filter['orderBy'], $filter['order'])
->paginate($filter['perPage']);
}
protected function getFilterData(array $filterData = []): array
{
$defaultArgs = [
'perPage' => 10,
'search' => '',
'orderBy' => 'id',
'order' => 'desc',
'with_deleted' => false,
];
return array_merge($defaultArgs, $filterData);
}
private function getRestaurantQuery(): Builder
{
return $this->getQuery()
->leftJoin('users as owner', 'restaurants.owner_id', '=', 'owner.id')
->leftJoin('users as assigned', 'restaurants.assigned_to', '=', 'assigned.id')
->leftJoin('themes', 'restaurants.theme_id', '=', 'themes.id')
->select([
'restaurants.id',
'restaurants.owner_id',
'owner.first_name as owner_name',
'restaurants.assigned_to',
'assigned.first_name as assigned_name',
'restaurants.name',
'restaurants.email',
'restaurants.address',
'restaurants.restaurant_type',
'restaurants.phone',
'restaurants.domain',
'restaurants.platform',
'restaurants.last_active_time',
'restaurants.logo',
'restaurants.status',
'restaurants.theme_id',
'themes.name as theme_name',
'restaurants.created_at',
'restaurants.deleted_at',
]);
}
protected function filterSearchQuery(Builder|EloquentBuilder $query, string $searchedText): Builder
{
$searchable = "%$searchedText%";
return $query->where('restaurants.name', 'LIKE', $searchable)
->orWhere('restaurants.email', 'LIKE', $searchable)
->orWhere('restaurants.restaurant_type', 'LIKE', $searchable)
->orWhere('restaurants.phone', 'LIKE', $searchable)
->orWhere('restaurants.domain', 'LIKE', $searchable);
}
/**
* @throws Exception
*/
public function getByColumn(string $columnName, $columnValue, array $selects = ['*']): ?object
{
$user = $this->getRestaurantQuery()
->where($columnName, $columnValue)
->first();
if (empty($user)) {
throw new Exception(
$this->getExceptionMessage(static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE),
Response::HTTP_NOT_FOUND
);
}
return $user;
}
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 create(array $data): object
{
try {
return DB::transaction(function () use ($data) {
// Step 1: Prepare data for DB (handles file uploads, default timestamps, owner_id, etc.)
$data = $this->prepareForDB($data);
// Step 2: Insert restaurant and get the ID
$restaurantId = $this->getQuery()->insertGetId($data);
$restaurant = Restaurant::find($restaurantId);
// Step 3: Owner info from currently authenticated user
$authUser = Auth::user();
$ownerData = [
'name' => $authUser->first_name,
'email' => $authUser->email,
'phone' => $authUser->phone,
'avatar' => $authUser->avatar,
'package_id' => $data['package_id'] ?? null,
'price' => $data['price'] ?? null,
];
// Step 4: Setup restaurant (owner assignment, settings, subscription, domain)
RestaurantSetupService::setup($restaurant, $ownerData, false);
// Step 5: Return the restaurant object
return $restaurant;
});
} catch (Exception $exception) {
// Rollback is automatic on exception in transaction closure
throw new Exception($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* @throws Exception
*/
public function prepareForDB(array $data, ?object $item = null): array
{
$data = parent::prepareForDB($data, $item);
if (empty($item)) {
$data['created_at'] = now();
$data['owner_id'] = getUserId();
$data['assigned_to'] = $this->getCurrentUserId();
$data['created_by'] = $this->getCurrentUserId();
$data['status'] = 1;
if (! empty($data['logo']) && $data['logo'] instanceof \Illuminate\Http\UploadedFile) {
$data['logo'] = fileUploader('restaurants/', 'png', $data['logo']);
}
} else {
if (! empty($data['logo']) && $data['logo'] instanceof \Illuminate\Http\UploadedFile) {
$data['logo'] = fileUploader('restaurants/', 'png', $data['logo'], $item->logo);
}
$data['updated_at'] = now();
}
return $data;
}
/**
* @throws Exception
*/
public function update(int $id, array $data): ?object
{
try {
$restaurant = Restaurant::find($id);
$data = $this->prepareForDB($data, $restaurant);
parent::update($id, $data);
return $this->getById($id);
} catch (Exception $exception) {
throw new Exception($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
protected function getExceptionMessages(): array
{
$exceptionMessages = parent::getExceptionMessages();
$userExceptionMessages = [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'Restaurant does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'Restaurant could not be deleted.',
];
return array_merge($exceptionMessages, $userExceptionMessages);
}
}

View File

@@ -0,0 +1,175 @@
<?php
namespace Modules\Authentication\Repositories;
use App\Abstracts\EntityRepository;
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Modules\Authentication\Models\SAASFaq;
use Symfony\Component\HttpFoundation\Response;
class SAASFaqRepository extends EntityRepository
{
public string $table = SAASFaq::TABLE_NAME;
protected array $fillableColumns = [
'question',
'answer',
'status',
'created_by',
'created_at',
'updated_at',
];
protected function getQuery(): Builder
{
return parent::getQuery();
}
public function getAll(array $filterData = []): Paginator
{
$filter = $this->getFilterData($filterData);
$query = $this->getSAASFaqQuery();
if (! $filter['with_deleted']) {
$query->whereNull("{$this->table}.deleted_at");
}
if (isset($filter['search']) && strlen($filter['search']) > 0) {
$query = $this->filterSearchQuery($query, $filter['search']);
}
return $query
->orderBy($filter['orderBy'], $filter['order'])
->paginate($filter['perPage']);
}
protected function getFilterData(array $filterData = []): array
{
$defaultArgs = [
'perPage' => 10,
'search' => '',
'orderBy' => 'id',
'order' => 'desc',
'with_deleted' => false,
];
return array_merge($defaultArgs, $filterData);
}
private function getSAASFaqQuery(): Builder
{
return $this->getQuery()
->select(
's_a_a_s_faqs.id',
's_a_a_s_faqs.question',
's_a_a_s_faqs.answer',
's_a_a_s_faqs.status',
's_a_a_s_faqs.created_at'
);
}
protected function filterSearchQuery(Builder|EloquentBuilder $query, string $searchedText): Builder
{
$searchable = "%$searchedText%";
return $query->where('s_a_a_s_faqs.question', 'LIKE', $searchable)
->orWhere('s_a_a_s_faqs.answer', 'LIKE', $searchable)
->orWhere('s_a_a_s_faqs.status', 'LIKE', $searchable);
}
/**
* @throws Exception
*/
public function getByColumn(string $columnName, $columnValue, array $selects = ['*']): ?object
{
$user = $this->getSAASFaqQuery()
->where($columnName, $columnValue)
->first();
if (empty($user)) {
throw new Exception(
$this->getExceptionMessage(static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE),
Response::HTTP_NOT_FOUND
);
}
return $user;
}
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 create(array $data): object
{
try {
$data = $this->prepareForDB($data);
$userId = $this->getQuery()->insertGetId($data);
$user = SAASFaq::find($userId);
return $user;
} catch (Exception $exception) {
throw new Exception($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* @throws Exception
*/
public function prepareForDB(array $data, ?object $item = null): array
{
$data = parent::prepareForDB($data, $item);
if (empty($item)) {
$data['created_at'] = now();
$data['created_by'] = $this->getCurrentUserId();
$data['status'] = 1;
} else {
$data['updated_at'] = now();
}
return $data;
}
/**
* @throws Exception
*/
public function update(int $id, array $data): ?object
{
try {
$user = SAASFaq::find($id);
$data = $this->prepareForDB($data, $user);
parent::update($id, $data);
return $this->getById($id);
} catch (Exception $exception) {
throw new Exception($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
protected function getExceptionMessages(): array
{
$exceptionMessages = parent::getExceptionMessages();
$userExceptionMessages = [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'SAASFaq does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'SAASFaq could not be deleted.',
];
return array_merge($exceptionMessages, $userExceptionMessages);
}
}

View File

@@ -0,0 +1,167 @@
<?php
namespace Modules\Authentication\Repositories;
use App\Abstracts\EntityRepository;
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Modules\Authentication\Models\SAASSubscription;
use Symfony\Component\HttpFoundation\Response;
class SAASSubscriptionRepository extends EntityRepository
{
public string $table = SAASSubscription::TABLE_NAME;
protected array $fillableColumns = [
'email',
'created_at',
'updated_at',
];
protected function getQuery(): Builder
{
return parent::getQuery();
}
public function getAll(array $filterData = []): Paginator
{
$filter = $this->getFilterData($filterData);
$query = $this->getSAASSubscriptionQuery();
if (! $filter['with_deleted']) {
$query->whereNull("{$this->table}.deleted_at");
}
if (isset($filter['search']) && strlen($filter['search']) > 0) {
$query = $this->filterSearchQuery($query, $filter['search']);
}
return $query
->orderBy($filter['orderBy'], $filter['order'])
->paginate($filter['perPage']);
}
protected function getFilterData(array $filterData = []): array
{
$defaultArgs = [
'perPage' => 10,
'search' => '',
'orderBy' => 'id',
'order' => 'desc',
'with_deleted' => false,
];
return array_merge($defaultArgs, $filterData);
}
private function getSAASSubscriptionQuery(): Builder
{
return $this->getQuery()
->select(
's_a_a_s_subscriptions.id',
's_a_a_s_subscriptions.email',
's_a_a_s_subscriptions.created_at'
);
}
protected function filterSearchQuery(Builder|EloquentBuilder $query, string $searchedText): Builder
{
$searchable = "%$searchedText%";
return $query->where('s_a_a_s_subscriptions.email', 'LIKE', $searchable)
->orWhere('s_a_a_s_subscriptions.created_at', 'LIKE', $searchable);
}
/**
* @throws Exception
*/
public function getByColumn(string $columnName, $columnValue, array $selects = ['*']): ?object
{
$user = $this->getSAASSubscriptionQuery()
->where($columnName, $columnValue)
->first();
if (empty($user)) {
throw new Exception(
$this->getExceptionMessage(static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE),
Response::HTTP_NOT_FOUND
);
}
return $user;
}
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 create(array $data): object
{
try {
$data = $this->prepareForDB($data);
$userId = $this->getQuery()->insertGetId($data);
$user = SAASSubscription::find($userId);
return $user;
} catch (Exception $exception) {
throw new Exception($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* @throws Exception
*/
public function prepareForDB(array $data, ?object $item = null): array
{
$data = parent::prepareForDB($data, $item);
if (empty($item)) {
$data['created_at'] = now();
} else {
$data['updated_at'] = now();
}
return $data;
}
/**
* @throws Exception
*/
public function update(int $id, array $data): ?object
{
try {
$user = SAASSubscription::find($id);
$data = $this->prepareForDB($data, $user);
parent::update($id, $data);
return $this->getById($id);
} catch (Exception $exception) {
throw new Exception($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
protected function getExceptionMessages(): array
{
$exceptionMessages = parent::getExceptionMessages();
$userExceptionMessages = [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'SAASSubscription does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'SAASSubscription could not be deleted.',
];
return array_merge($exceptionMessages, $userExceptionMessages);
}
}

View File

@@ -0,0 +1,289 @@
<?php
namespace Modules\Authentication\Repositories;
use App\Abstracts\EntityRepository;
use Exception;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Hash;
use Modules\Authentication\Models\User;
use Symfony\Component\HttpFoundation\Response;
class UserRepository extends EntityRepository
{
public string $table = User::TABLE_NAME;
protected array $fillableColumns = [
'restaurant_id',
'first_name',
'last_name',
'username',
'email',
'phone',
'password',
'otp_code',
'isVerified',
'email_verified_at',
'address',
'avatar',
'role_id',
'user_type',
'facebook',
'twitter',
'linkedin',
'google_plus',
'nid',
'platform',
'device_info',
'last_active_time',
'status',
'bio',
'fcm_token',
'qr_code',
'department_id',
'designation_id',
'salary_type_id',
'address',
'gender',
'date_of_birth',
'joining_date',
'basic_salary',
'created_by',
'deleted_at',
];
protected function getQuery(): Builder
{
return parent::getQuery();
}
public function getAll(array $filterData = []): Paginator
{
$filter = $this->getFilterData($filterData);
$query = $this->getUserQuery();
if (! $filter['with_deleted']) {
$query->whereNull("{$this->table}.deleted_at");
}
if (isset($filter['search']) && strlen($filter['search']) > 0) {
$query = $this->filterSearchQuery($query, $filter['search']);
}
if (! empty($filter['user_type'])) {
$query->where("{$this->table}.user_type", $filter['user_type']);
}
return $query
->orderBy($filter['orderBy'], $filter['order'])
->paginate($filter['perPage']);
}
protected function getFilterData(array $filterData = []): array
{
$defaultArgs = [
'perPage' => 10,
'search' => '',
'orderBy' => 'id',
'order' => 'desc',
'with_deleted' => false,
'user_type' => $filterData['user_type'] ?? null, // ⬅️ ADD THIS
];
return array_merge($defaultArgs, $filterData);
}
private function getUserQuery(): Builder
{
return $this->getQuery()
->whereNotIn('id', [3])
->select(
'users.id',
'users.restaurant_id',
'users.first_name',
'users.last_name',
'users.username',
'users.email',
'users.phone',
'users.isVerified',
'users.email_verified_at',
'users.address',
'users.avatar',
'users.role_id',
'users.user_type',
'users.facebook',
'users.twitter',
'users.linkedin',
'users.google_plus',
'users.nid',
'users.platform',
'users.device_info',
'users.last_active_time',
'users.status',
'users.bio',
'users.fcm_token',
'users.qr_code',
'users.department_id',
'users.designation_id',
'users.salary_type_id',
'users.address',
'users.gender',
'users.date_of_birth',
'users.joining_date',
'users.basic_salary',
'users.created_by',
'users.deleted_at'
);
}
protected function filterSearchQuery(Builder|EloquentBuilder $query, string $searchedText): Builder
{
$searchable = "%$searchedText%";
return $query->where('users.first_name', 'LIKE', $searchable)
->orWhere('users.last_name', 'LIKE', $searchable)
->orWhere('users.email', 'LIKE', $searchable)
->orWhere('users.phone', 'LIKE', $searchable)
->orWhere('users.status', 'LIKE', $searchable);
}
/**
* @throws Exception
*/
public function getByColumn(string $columnName, $columnValue, array $selects = ['*']): ?object
{
$user = $this->getUserQuery()
->where($columnName, $columnValue)
->first();
if (empty($user)) {
throw new Exception(
$this->getExceptionMessage(static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE),
Response::HTTP_NOT_FOUND
);
}
return $user;
}
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 create(array $data): object
{
try {
$data['password'] = empty($data['password']) ? '123456' : $data['password'];
$data = $this->prepareForDB($data);
$userId = $this->getQuery()->insertGetId($data);
$user = User::find($userId);
return $user;
} catch (Exception $exception) {
throw new Exception($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* @throws Exception
*/
public function prepareForDB(array $data, ?object $item = null): array
{
$data = parent::prepareForDB($data, $item);
if (empty($item)) {
$data['created_at'] = now();
$data['created_by'] = $this->getCurrentUserId();
$data['restaurant_id'] = $this->getCurrentRestaurantId();
$data['status'] = 1;
if (! empty($data['avatar']) && $data['avatar'] instanceof \Illuminate\Http\UploadedFile) {
$data['avatar'] = fileUploader('users/', 'png', $data['avatar']);
}
} else {
if (! empty($data['avatar']) && $data['avatar'] instanceof \Illuminate\Http\UploadedFile) {
$data['avatar'] = fileUploader('users/', 'png', $data['avatar'], $item->avatar);
}
$data['updated_at'] = now();
}
if (! empty($data['password'])) {
$data['password'] = Hash::make($data['password']);
} else {
unset($data['password']);
}
return $data;
}
/**
* @throws Exception
*/
public function update(int $id, array $data): ?object
{
try {
$user = User::find($id);
$data = $this->prepareForDB($data, $user);
parent::update($id, $data);
return $this->getById($user->id);
} catch (Exception $exception) {
throw new Exception($exception->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* @throws Exception
*/
public function getDropdown(): array
{
$dropdowns = parent::getDropdown();
$dropdownsData = [];
foreach ($dropdowns as $dropdownItem) {
$dropdownItem->name = $dropdownItem->first_name.' '.$dropdownItem->last_name.' #'.$dropdownItem->id;
unset($dropdownItem->first_name, $dropdownItem->last_name, $dropdownItem->phone);
$dropdownsData[] = $dropdownItem;
}
return $dropdownsData;
}
protected function getExceptionMessages(): array
{
$exceptionMessages = parent::getExceptionMessages();
$userExceptionMessages = [
static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'User does not exist.',
static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'User could not be deleted.',
];
return array_merge($exceptionMessages, $userExceptionMessages);
}
protected function getDropdownSelectableColumns(): array
{
return [
'first_name',
'last_name',
];
}
protected function getOrderByColumnWithOrders(): array
{
return [
'first_name' => 'asc',
];
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace Modules\Authentication\Services;
use Modules\Authentication\Interfaces\PermissionInterface;
use Spatie\Permission\Models\Permission;
final class PermissionService implements PermissionInterface
{
public function __construct(
protected Permission $model
) {}
public function index($request, int $perPage = 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($perPage);
}
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)
{
return $this->model::where('id', $id)->first();
}
public function create(array $data)
{
return $this->model::create($data);
}
public function update(int $id, array $data)
{
$model = $this->model::where('id', $id)->first();
$model->update($data);
return $model;
}
public function delete(int $id)
{
$model = $this->model::where('id', $id)->first();
return $model->delete();
}
}

View File

@@ -0,0 +1,323 @@
<?php
namespace Modules\Authentication\Services;
use App\Mail\RestaurantOnboardingMail;
use Carbon\Carbon;
use Exception;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Modules\Authentication\Models\Package;
use Modules\Authentication\Models\Restaurant;
use Modules\Authentication\Models\Subscription;
use Modules\Authentication\Models\SubscriptionItem;
use Modules\Authentication\Models\User;
class RestaurantSetupService
{
/**
* Initialize restaurant related setup including default settings, subscriptions, and domain.
*
* @param array $ownerData
* Expected keys: name, email, phone, password, avatar (optional), package_id, price
*/
public static function setup(Restaurant $restaurant, array $ownerData, bool $sendMail = true): User
{
// Step 1: Check if user already exists
$user = User::where('email', $ownerData['email'])->first();
if (! $user) {
// Create a new user only if not exists
$user = User::create([
'first_name' => $ownerData['name'],
'email' => $ownerData['email'],
'phone' => $ownerData['phone'],
'password' => Hash::make($ownerData['password']),
'avatar' => $ownerData['avatar'] ?? null,
'role_id' => 2,
'user_type' => 'System Admin',
]);
// Assign role
$role = \Spatie\Permission\Models\Role::find(2);
$user->assignRole($role);
}
// Step 2: Assign this user as restaurant owner
$restaurant->owner_id = $user->id;
$restaurant->save();
// Step 3: Insert default settings
self::insertDefaultSettings($restaurant);
// Step 4: Create Subscription & SubscriptionItem if package_id is provided
$package = Package::first();
$startDate = Carbon::now()->startOfDay();
$endDate = Carbon::now()->addDays($package->duration - 1)->endOfDay();
// Create subscription
$subscription = Subscription::updateOrCreate([
'user_id' => $user->id,
'restaurant_id' => $restaurant->id,
'package_id' => $package->id,
], [
'start_date' => $startDate,
'end_date' => $endDate,
'status' => 1,
]);
// Create subscription item
SubscriptionItem::updateOrCreate([
'user_id' => $user->id,
'restaurant_id' => $restaurant->id,
'subscription_id' => $subscription->id,
'package_id' => $package->id,
], [
'amount' => (float) $package->price,
'start_date' => $startDate,
'end_date' => $endDate,
]);
// Step 5: Add domain to Vercel if restaurant domain exists
if (! empty($restaurant->domain)) {
try {
$vercelDomainResponse = self::addDomainToVercel($restaurant->domain);
if (! $vercelDomainResponse['success']) {
Log::warning('Vercel domain add failed', [
'domain' => $restaurant->domain,
'response' => $vercelDomainResponse,
]);
}
} catch (Exception $e) {
Log::error('Vercel domain add exception: '.$e->getMessage());
}
}
// Step 6: Send onboarding mail only if user is new
if ($sendMail && $user->wasRecentlyCreated) {
Mail::to($ownerData['email'])->send(new RestaurantOnboardingMail([
'restaurant_name' => $restaurant->name,
'restaurant_id' => $restaurant->id,
'email' => $ownerData['email'],
'password' => $ownerData['password'],
'package_name' => $package->name ?? 'Trial Package',
'trial_days' => $package->duration ?? 14,
'trial_end_date' => now()->addDays($package->duration ?? 14)->format('Y-m-d'),
'login_url' => url('/login'),
]));
}
return $user;
}
/**
* Example placeholder for adding domain to Vercel
*
* @param string $domain
* @return array
*/
public static function addDomainToVercel($fullDomain)
{
$client = new Client;
$projectId = get_saas_option('vercel_project_id');
$token = get_saas_option('vercel_token');
$url = "https://api.vercel.com/v9/projects/{$projectId}/domains";
try {
$response = $client->post($url, [
'headers' => [
'Authorization' => 'Bearer '.$token,
'Content-Type' => 'application/json',
],
'json' => [
'name' => $fullDomain,
],
]);
return [
'success' => true,
'data' => json_decode($response->getBody(), true),
];
} catch (\GuzzleHttp\Exception\ClientException $e) {
return [
'success' => false,
'error_type' => 'client',
'message' => $e->getMessage(),
];
} catch (Exception $e) {
return [
'success' => false,
'error_type' => 'general',
'message' => $e->getMessage(),
];
}
}
private static function insertDefaultSettings(Restaurant $restaurant)
{
DB::table('settings')->insert([
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'restaurant_name', 'option_value' => 'Restaurant'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'site_title', 'option_value' => 'restaurant'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'phone', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'email', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'language', 'option_value' => 'en'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'google_map', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'address', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'on_google_map', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'restaurant_code', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'timezone', 'option_value' => 'Asia/Dhaka'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'currency_symbol', 'option_value' => '$'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'mail_type', 'option_value' => 'mail'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'logo', 'option_value' => 'logo.png'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'disabled_website', 'option_value' => 'no'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'copyright_text', 'option_value' => '&copy; Copyright 2025. All Rights Reserved by FueDevs LTD'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'facebook_link', 'option_value' => 'https://www.facebook.com/'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'google_plus_link', 'option_value' => 'https://www.google.com/'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'youtube_link', 'option_value' => 'https://www.youtube.com/'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'whats_app_link', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'twitter_link', 'option_value' => 'https://www.twitter.com'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'eiin_code', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'sms_gateway', 'option_value' => 'twilio'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'bulk_sms_api_key', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'bulk_sms_sender_id', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'twilio_sid', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'twilio_token', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'twilio_from_number', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'header_notice', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'app_version', 'option_value' => '1.0.0'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'app_url', 'option_value' => 'drive-link'],
// 🎨 Restaurant Identity & Display
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'tagline', 'option_value' => 'Delicious Food, Fresh Taste'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'favicon', 'option_value' => 'favicon.png'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'theme_color', 'option_value' => '#ff6b00'],
['restaurant_id' => $restaurant->id, 'type' => 'general', 'option_key' => 'background_image', 'option_value' => 'bg.jpg'],
// 💰 Finance / POS / Invoice
['restaurant_id' => $restaurant->id, 'type' => 'pos', 'option_key' => 'tax_type', 'option_value' => 'exclusive'],
['restaurant_id' => $restaurant->id, 'type' => 'pos', 'option_key' => 'tax_percentage', 'option_value' => '10'],
['restaurant_id' => $restaurant->id, 'type' => 'pos', 'option_key' => 'service_charge', 'option_value' => '5'],
['restaurant_id' => $restaurant->id, 'type' => 'pos', 'option_key' => 'default_currency', 'option_value' => 'USD'],
['restaurant_id' => $restaurant->id, 'type' => 'pos', 'option_key' => 'billing_prefix', 'option_value' => 'INV-'],
['restaurant_id' => $restaurant->id, 'type' => 'pos', 'option_key' => 'invoice_footer', 'option_value' => 'Thank you! Visit again.'],
['restaurant_id' => $restaurant->id, 'type' => 'pos', 'option_key' => 'enable_kitchen_print', 'option_value' => 'yes'],
['restaurant_id' => $restaurant->id, 'type' => 'pos', 'option_key' => 'enable_customer_copy', 'option_value' => 'yes'],
// 🚚 Online Ordering & Delivery
['restaurant_id' => $restaurant->id, 'type' => 'order', 'option_key' => 'enable_online_order', 'option_value' => 'yes'],
['restaurant_id' => $restaurant->id, 'type' => 'order', 'option_key' => 'delivery_charge', 'option_value' => '50'],
['restaurant_id' => $restaurant->id, 'type' => 'order', 'option_key' => 'minimum_order_amount', 'option_value' => '100'],
['restaurant_id' => $restaurant->id, 'type' => 'order', 'option_key' => 'auto_accept_order', 'option_value' => 'no'],
['restaurant_id' => $restaurant->id, 'type' => 'order', 'option_key' => 'estimated_preparation_time', 'option_value' => '30'],
// 🔔 Notifications / Integrations
['restaurant_id' => $restaurant->id, 'type' => 'integration', 'option_key' => 'slack_webhook_url', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'integration', 'option_key' => 'telegram_bot_token', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'integration', 'option_key' => 'telegram_chat_id', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'integration', 'option_key' => 'twilio_sms_enabled', 'option_value' => 'yes'],
['restaurant_id' => $restaurant->id, 'type' => 'integration', 'option_key' => 'email_notifications', 'option_value' => 'yes'],
['restaurant_id' => $restaurant->id, 'type' => 'integration', 'option_key' => 'whatsapp_notifications', 'option_value' => 'no'],
// 🧾 Reports & Logs
['restaurant_id' => $restaurant->id, 'type' => 'system', 'option_key' => 'auto_backup', 'option_value' => 'daily'],
['restaurant_id' => $restaurant->id, 'type' => 'system', 'option_key' => 'report_timezone', 'option_value' => 'Asia/Dhaka'],
['restaurant_id' => $restaurant->id, 'type' => 'system', 'option_key' => 'data_retention_days', 'option_value' => '365'],
// 💻 UI/UX Preferences
['restaurant_id' => $restaurant->id, 'type' => 'ui', 'option_key' => 'sidebar_collapsed', 'option_value' => 'no'],
['restaurant_id' => $restaurant->id, 'type' => 'ui', 'option_key' => 'dark_mode', 'option_value' => 'no'],
['restaurant_id' => $restaurant->id, 'type' => 'ui', 'option_key' => 'default_dashboard', 'option_value' => 'sales'],
// 💳 Payment Gateways
['restaurant_id' => $restaurant->id, 'type' => 'payment', 'option_key' => 'razorpay_key', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'payment', 'option_key' => 'razorpay_secret', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'payment', 'option_key' => 'stripe_key', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'payment', 'option_key' => 'stripe_secret', 'option_value' => ''],
['restaurant_id' => $restaurant->id, 'type' => 'payment', 'option_key' => 'cash_on_delivery', 'option_value' => 'yes'],
// 👨‍🍳 Kitchen & Staff
['restaurant_id' => $restaurant->id, 'type' => 'staff', 'option_key' => 'max_table_capacity', 'option_value' => '10'],
['restaurant_id' => $restaurant->id, 'type' => 'staff', 'option_key' => 'default_shift_start', 'option_value' => '09:00'],
['restaurant_id' => $restaurant->id, 'type' => 'staff', 'option_key' => 'default_shift_end', 'option_value' => '23:00'],
['restaurant_id' => $restaurant->id, 'type' => 'staff', 'option_key' => 'auto_logout_idle_minutes', 'option_value' => '60'],
// 🎨 Color Combination
['restaurant_id' => $restaurant->id, 'type' => 'system_color', 'option_key' => 'primary_color', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'system_color', 'option_key' => 'secondary_color', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'system_color', 'option_key' => 'primary_container_color', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'system_color', 'option_key' => 'dark_primary_color', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'system_color', 'option_key' => 'dark_secondary_color', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'system_color', 'option_key' => 'dark_container_color', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'system_color', 'option_key' => 'text_color', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'system_color', 'option_key' => 'dark_text_color', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'system_color', 'option_key' => 'sidebar_selected_bg_color', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'system_color', 'option_key' => 'sidebar_selected_text_color', 'option_value' => null],
// 🚚 Delivery / Online features
['restaurant_id' => $restaurant->id, 'type' => 'delivery', 'option_key' => 'is_online', 'option_value' => false],
['restaurant_id' => $restaurant->id, 'type' => 'delivery', 'option_key' => 'latitude', 'option_value' => '23.8103'],
['restaurant_id' => $restaurant->id, 'type' => 'delivery', 'option_key' => 'longitude', 'option_value' => '90.4125'],
['restaurant_id' => $restaurant->id, 'type' => 'delivery', 'option_key' => 'delivery_radius_km', 'option_value' => 5.00],
['restaurant_id' => $restaurant->id, 'type' => 'delivery', 'option_key' => 'delivery_fee', 'option_value' => 0],
['restaurant_id' => $restaurant->id, 'type' => 'delivery', 'option_key' => 'delivery_partner_count', 'option_value' => 0],
['restaurant_id' => $restaurant->id, 'type' => 'delivery', 'option_key' => 'delivery_time_avg', 'option_value' => 30],
['restaurant_id' => $restaurant->id, 'type' => 'delivery', 'option_key' => 'pickup_enabled', 'option_value' => true],
// 🕒 Operational hours & capacity
['restaurant_id' => $restaurant->id, 'type' => 'operational', 'option_key' => 'opening_time', 'option_value' => '09:00:00'],
['restaurant_id' => $restaurant->id, 'type' => 'operational', 'option_key' => 'closing_time', 'option_value' => '22:00:00'],
['restaurant_id' => $restaurant->id, 'type' => 'operational', 'option_key' => 'auto_accept_orders', 'option_value' => false],
['restaurant_id' => $restaurant->id, 'type' => 'operational', 'option_key' => 'pre_order_enabled', 'option_value' => false],
['restaurant_id' => $restaurant->id, 'type' => 'operational', 'option_key' => 'max_order_capacity', 'option_value' => 50],
// 📊 Analytics / reviews
['restaurant_id' => $restaurant->id, 'type' => 'analytics', 'option_key' => 'avg_rating', 'option_value' => 0.00],
['restaurant_id' => $restaurant->id, 'type' => 'analytics', 'option_key' => 'review_count', 'option_value' => 0],
['restaurant_id' => $restaurant->id, 'type' => 'analytics', 'option_key' => 'total_orders', 'option_value' => 0],
['restaurant_id' => $restaurant->id, 'type' => 'analytics', 'option_key' => 'last_order_time', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'analytics', 'option_key' => 'last_active_time', 'option_value' => null],
// 🎯 Marketing & social
['restaurant_id' => $restaurant->id, 'type' => 'marketing', 'option_key' => 'loyalty_points_enabled', 'option_value' => false],
['restaurant_id' => $restaurant->id, 'type' => 'marketing', 'option_key' => 'offers_enabled', 'option_value' => false],
['restaurant_id' => $restaurant->id, 'type' => 'marketing', 'option_key' => 'social_media_links', 'option_value' => json_encode([
'facebook' => '',
'instagram' => '',
'twitter' => '',
'linkedin' => '',
])],
// 💻 Future-proof / extra
['restaurant_id' => $restaurant->id, 'type' => 'system', 'option_key' => 'settings', 'option_value' => json_encode([])],
['restaurant_id' => $restaurant->id, 'type' => 'system', 'option_key' => 'uuid', 'option_value' => uniqid('rest_')],
['restaurant_id' => $restaurant->id, 'type' => 'email_config', 'option_key' => 'email_smtp_host', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'email_config', 'option_key' => 'email_smtp_port', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'email_config', 'option_key' => 'email_smtp_username', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'email_config', 'option_key' => 'email_smtp_password', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'email_config', 'option_key' => 'email_smtp_encryption', 'option_value' => null],
// SMS Config
['restaurant_id' => $restaurant->id, 'type' => 'sms_config', 'option_key' => 'twilio_api_key', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'sms_config', 'option_key' => 'twilio_api_secret', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'sms_config', 'option_key' => 'twilio_sender_id', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'sms_config', 'option_key' => 'twilio_api_url', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'sms_config', 'option_key' => 'twilio_is_default', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'sms_config', 'option_key' => 'nexmo_api_key', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'sms_config', 'option_key' => 'nexmo_api_secret', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'sms_config', 'option_key' => 'nexmo_sender_id', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'sms_config', 'option_key' => 'nexmo_api_url', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'sms_config', 'option_key' => 'nexmo_is_default', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'sms_config', 'option_key' => 'muthofun_api_key', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'sms_config', 'option_key' => 'smsglobal_api_key', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'sms_config', 'option_key' => 'smsglobal_api_secret', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'sms_config', 'option_key' => 'smsglobal_sender_id', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'sms_config', 'option_key' => 'smsglobal_api_url', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'sms_config', 'option_key' => 'smsglobal_extra_key', 'option_value' => null],
['restaurant_id' => $restaurant->id, 'type' => 'sms_config', 'option_key' => 'smsglobal_is_default', 'option_value' => null],
]);
}
}

Some files were not shown because too many files have changed in this diff Show More