initial: bootstrap from BukidBountyApp base
This commit is contained in:
9
app/Http/Controllers/AbstractController.php
Normal file
9
app/Http/Controllers/AbstractController.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class AbstractController
|
||||
{
|
||||
}
|
||||
751
app/Http/Controllers/Accounting/AccountingController.php
Normal file
751
app/Http/Controllers/Accounting/AccountingController.php
Normal file
@@ -0,0 +1,751 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Accounting;
|
||||
|
||||
use App\Http\Controllers\AbstractController;
|
||||
use App\Models\Accounting\Account;
|
||||
use App\Models\Accounting\AccountTransaction;
|
||||
use App\Models\Market\Store;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Enums\UserActions;
|
||||
use App\Enums\UserTypes;
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Support\ModuleHelper;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\DB;
|
||||
use Hyperf\Stringable\Str;
|
||||
|
||||
class AccountingController extends AbstractController
|
||||
{
|
||||
// ─── helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
private function resolveType(): UserTypes
|
||||
{
|
||||
$user = Auth::user();
|
||||
return $user->acct_type instanceof UserTypes
|
||||
? $user->acct_type
|
||||
: (UserTypes::tryFrom($user->acct_type) ?? UserTypes::PUBLIC);
|
||||
}
|
||||
|
||||
private function isBig3(UserTypes $t): bool
|
||||
{
|
||||
return in_array($t, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR], true);
|
||||
}
|
||||
|
||||
private function isStoreLevel(UserTypes $t): bool
|
||||
{
|
||||
return in_array($t, [UserTypes::STORE_OWNER, UserTypes::STORE_MANAGER], true);
|
||||
}
|
||||
|
||||
/** IDs of stores the current user owns or manages (including descendants). */
|
||||
private function userStoreIds(): array
|
||||
{
|
||||
$user = Auth::user();
|
||||
$allowedUserIds = array_merge([$user->id], $user->getAllDescendants()->pluck('id')->toArray());
|
||||
|
||||
return Store::where(function ($q) use ($allowedUserIds) {
|
||||
$q->whereIn('owner_id', $allowedUserIds)
|
||||
->orWhereIn('manager_id', $allowedUserIds)
|
||||
->orWhereHas('managers', function ($mq) use ($allowedUserIds) {
|
||||
$mq->whereIn('user_id', $allowedUserIds);
|
||||
});
|
||||
})->pluck('id')->toArray();
|
||||
}
|
||||
|
||||
/** Apply store_id scope to a query based on the caller's role. */
|
||||
private function scopeAccounts($query, array $storeIds, bool $isBig3)
|
||||
{
|
||||
if ($isBig3) {
|
||||
return $query->whereNull('store_id');
|
||||
}
|
||||
return $query->whereIn('store_id', $storeIds);
|
||||
}
|
||||
|
||||
/** Check store-level access. Returns false if the accounting_store module is disabled. */
|
||||
private function guardStore(): bool
|
||||
{
|
||||
return ModuleHelper::isEnabled('accounting_store');
|
||||
}
|
||||
|
||||
// ─── public endpoints ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Full Chart of Accounts tree.
|
||||
*/
|
||||
public function getAccountsTree(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$type = $this->resolveType();
|
||||
$isBig3 = $this->isBig3($type);
|
||||
|
||||
if (!$isBig3 && !$this->guardStore()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$storeIds = $isBig3 ? [] : $this->userStoreIds();
|
||||
|
||||
$base = Account::whereNull('parent_id')->where('is_active', true);
|
||||
$base = $this->scopeAccounts($base, $storeIds, $isBig3);
|
||||
|
||||
$accounts = $base
|
||||
->with(['children' => function ($q) use ($storeIds, $isBig3) {
|
||||
$q = $q->where('is_active', true);
|
||||
$q = $this->scopeAccounts($q, $storeIds, $isBig3);
|
||||
$q->with(['children' => function ($q2) use ($storeIds, $isBig3) {
|
||||
$q2 = $q2->where('is_active', true);
|
||||
$q2 = $this->scopeAccounts($q2, $storeIds, $isBig3);
|
||||
}]);
|
||||
}])
|
||||
->orderBy('id')
|
||||
->get();
|
||||
|
||||
return response()->json(['success' => true, 'data' => $accounts]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Leaf accounts for the Daily Entry form.
|
||||
*/
|
||||
public function getLeafAccounts(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$type = $this->resolveType();
|
||||
$isBig3 = $this->isBig3($type);
|
||||
|
||||
if (!$isBig3 && !$this->guardStore()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$storeIds = $isBig3 ? [] : $this->userStoreIds();
|
||||
|
||||
$base = Account::where('is_active', true)->whereDoesntHave('children');
|
||||
$base = $this->scopeAccounts($base, $storeIds, $isBig3);
|
||||
|
||||
$leafAccounts = $base
|
||||
->with('parent.parent')
|
||||
->orderBy('id')
|
||||
->get();
|
||||
|
||||
return response()->json(['success' => true, 'data' => $leafAccounts]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Daily transactions for a given date.
|
||||
*/
|
||||
public function getDailyTransactions(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$type = $this->resolveType();
|
||||
$isBig3 = $this->isBig3($type);
|
||||
|
||||
if (!$isBig3 && !$this->guardStore()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$storeIds = $isBig3 ? [] : $this->userStoreIds();
|
||||
$date = $request->input('date', date('Y-m-d'));
|
||||
|
||||
$accountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id');
|
||||
|
||||
$transactions = AccountTransaction::with(['account'])
|
||||
->whereIn('account_id', $accountIds)
|
||||
->whereDate('transaction_date', $date)
|
||||
->get();
|
||||
|
||||
$mapped = [];
|
||||
foreach ($transactions as $txn) {
|
||||
$mapped[$txn->account_id] = [
|
||||
'id' => $txn->id,
|
||||
'amount' => $txn->amount,
|
||||
'notes' => $txn->notes,
|
||||
];
|
||||
}
|
||||
|
||||
return response()->json(['success' => true, 'date' => $date, 'data' => $mapped]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk save daily transactions.
|
||||
*/
|
||||
public function saveDailyTransactions(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$type = $this->resolveType();
|
||||
$isBig3 = $this->isBig3($type);
|
||||
|
||||
if (!$isBig3 && !$this->guardStore()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$storeIds = $isBig3 ? [] : $this->userStoreIds();
|
||||
$date = $request->input('date');
|
||||
$entries = $request->input('entries', []);
|
||||
$userId = Auth::id();
|
||||
|
||||
// Allowed account IDs for this user (prevent writing to other stores' accounts)
|
||||
$allowedAccountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id')->flip()->all();
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
foreach ($entries as $accountId => $entryData) {
|
||||
if (!array_key_exists($accountId, $allowedAccountIds)) {
|
||||
continue; // skip accounts not owned by this user
|
||||
}
|
||||
|
||||
$amount = (float)($entryData['amount'] ?? 0);
|
||||
$account = Account::find($accountId);
|
||||
if (!$account) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$flow = $account->default_flow
|
||||
?: ((strtoupper((string) $account->type) === 'REVENUE' || strtoupper((string) $account->type) === 'LIABILITY') ? 'INCOME' : 'EXPENSE');
|
||||
|
||||
$txn = AccountTransaction::where('account_id', $accountId)
|
||||
->whereDate('transaction_date', $date)
|
||||
->first();
|
||||
|
||||
if ($amount == 0) {
|
||||
if ($txn) {
|
||||
$txn->delete();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($txn) {
|
||||
$txn->amount = $amount;
|
||||
$txn->notes = $entryData['notes'] ?? null;
|
||||
$txn->updated_by = $userId;
|
||||
$txn->save();
|
||||
} else {
|
||||
AccountTransaction::create([
|
||||
'hashkey' => Str::random(32),
|
||||
'account_id' => $accountId,
|
||||
'amount' => $amount,
|
||||
'flow' => $flow,
|
||||
'transaction_date' => $date,
|
||||
'notes' => $entryData['notes'] ?? null,
|
||||
'created_by' => $userId,
|
||||
'updated_by' => $userId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
DB::commit();
|
||||
return response()->json(['success' => true, 'message' => 'Daily transactions saved.']);
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return response()->json(['success' => false, 'message' => 'Error saving: ' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transaction list for CRUD tab.
|
||||
*/
|
||||
public function listTransactions(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$type = $this->resolveType();
|
||||
$isBig3 = $this->isBig3($type);
|
||||
|
||||
if (!$isBig3 && !$this->guardStore()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$storeIds = $isBig3 ? [] : $this->userStoreIds();
|
||||
$accountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id');
|
||||
|
||||
$query = AccountTransaction::with(['account', 'creator', 'updater'])
|
||||
->whereIn('account_id', $accountIds);
|
||||
|
||||
if ($request->filled('account_id')) {
|
||||
$query->where('account_id', $request->account_id);
|
||||
}
|
||||
if ($request->filled('date_from')) {
|
||||
$query->whereDate('transaction_date', '>=', $request->date_from);
|
||||
}
|
||||
if ($request->filled('date_to')) {
|
||||
$query->whereDate('transaction_date', '<=', $request->date_to);
|
||||
}
|
||||
|
||||
$transactions = $query->orderBy('transaction_date', 'desc')->paginate(50);
|
||||
|
||||
return response()->json(['success' => true, 'data' => $transactions]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete single transaction.
|
||||
*/
|
||||
public function deleteTransaction(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$type = $this->resolveType();
|
||||
$isBig3 = $this->isBig3($type);
|
||||
$storeIds = $isBig3 ? [] : $this->userStoreIds();
|
||||
$allowedAccountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id')->all();
|
||||
|
||||
$txn = AccountTransaction::find($request->id);
|
||||
if ($txn && in_array($txn->account_id, $allowedAccountIds)) {
|
||||
$txn->delete();
|
||||
}
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Monthly matrix report (Rows = Days, Columns = Accounts).
|
||||
*/
|
||||
public function getMonthlyReport(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$type = $this->resolveType();
|
||||
$isBig3 = $this->isBig3($type);
|
||||
|
||||
if (!$isBig3 && !$this->guardStore()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$storeIds = $isBig3 ? [] : $this->userStoreIds();
|
||||
$year = $request->input('year', date('Y'));
|
||||
$month = $request->input('month', date('m'));
|
||||
|
||||
$accountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id')->all();
|
||||
|
||||
$transactions = AccountTransaction::whereYear('transaction_date', $year)
|
||||
->whereMonth('transaction_date', $month)
|
||||
->whereIn('account_id', $accountIds)
|
||||
->get();
|
||||
|
||||
$daysInMonth = cal_days_in_month(CAL_GREGORIAN, (int)$month, (int)$year);
|
||||
$matrix = [];
|
||||
for ($i = 1; $i <= $daysInMonth; $i++) {
|
||||
$matrix[$i] = [];
|
||||
}
|
||||
|
||||
foreach ($transactions as $txn) {
|
||||
$day = (int)date('d', strtotime($txn->transaction_date));
|
||||
$matrix[$day][$txn->account_id] = ($matrix[$day][$txn->account_id] ?? 0) + $txn->amount;
|
||||
}
|
||||
|
||||
$columnTotals = [];
|
||||
foreach ($transactions as $txn) {
|
||||
$columnTotals[$txn->account_id] = ($columnTotals[$txn->account_id] ?? 0) + $txn->amount;
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'year' => $year,
|
||||
'month' => $month,
|
||||
'days_in_month' => $daysInMonth,
|
||||
'matrix' => $matrix,
|
||||
'column_totals' => $columnTotals,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new account node. For store-level users the account is
|
||||
* automatically scoped to their store (first owned store).
|
||||
*/
|
||||
public function createAccount(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$type = $this->resolveType();
|
||||
$isBig3 = $this->isBig3($type);
|
||||
|
||||
if (!$isBig3 && !$this->guardStore()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$name = trim((string) $request->input('name', ''));
|
||||
$accountType = strtoupper(trim((string) $request->input('type', '')));
|
||||
$defaultFlow = strtoupper(trim((string) $request->input('default_flow', '')));
|
||||
$parentId = $request->input('parent_id') ?: null;
|
||||
$description = $request->input('description') ?: null;
|
||||
|
||||
if ($name === '' || $accountType === '') {
|
||||
return response()->json(['success' => false, 'message' => 'Name and type are required.'], 422);
|
||||
}
|
||||
if (!in_array($defaultFlow, ['INCOME', 'EXPENSE'], true)) {
|
||||
$defaultFlow = in_array($accountType, ['REVENUE', 'LIABILITY']) ? 'INCOME' : 'EXPENSE';
|
||||
}
|
||||
if ($parentId !== null && !Account::where('id', $parentId)->exists()) {
|
||||
return response()->json(['success' => false, 'message' => 'Parent account not found.'], 422);
|
||||
}
|
||||
|
||||
// Determine store_id: null for Big3 (global), first store for store-level users
|
||||
$storeId = null;
|
||||
if (!$isBig3) {
|
||||
$storeIds = $this->userStoreIds();
|
||||
if (empty($storeIds)) {
|
||||
return response()->json(['success' => false, 'message' => 'No store found for your account. Create a store first.'], 422);
|
||||
}
|
||||
// If a parent is supplied, inherit its store_id; otherwise use the request-supplied store_id or the user's first store
|
||||
if ($parentId) {
|
||||
$parent = Account::find($parentId);
|
||||
$storeId = $parent?->store_id ?? $storeIds[0];
|
||||
} else {
|
||||
$requestedStoreId = $request->input('store_id');
|
||||
$storeId = ($requestedStoreId && in_array($requestedStoreId, $storeIds))
|
||||
? (int)$requestedStoreId
|
||||
: $storeIds[0];
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$account = Account::create([
|
||||
'hashkey' => Str::random(40),
|
||||
'parent_id' => $parentId,
|
||||
'store_id' => $storeId,
|
||||
'type' => $accountType,
|
||||
'default_flow' => $defaultFlow,
|
||||
'name' => $name,
|
||||
'description' => $description,
|
||||
'is_active' => true,
|
||||
'created_by' => Auth::id(),
|
||||
'updated_by' => Auth::id(),
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
\Hypervel\Support\Facades\Log::error('createAccount failed', [
|
||||
'message' => $e->getMessage(),
|
||||
'name' => $name,
|
||||
'type' => $accountType,
|
||||
'parent_id' => $parentId,
|
||||
'store_id' => $storeId,
|
||||
'user_id' => Auth::id(),
|
||||
]);
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Could not create account: ' . $e->getMessage(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
return response()->json(['success' => true, 'data' => $account]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update editable fields (name, type, default_flow, description).
|
||||
*/
|
||||
public function updateAccount(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$account = Account::find($request->input('id'));
|
||||
if (!$account) {
|
||||
return response()->json(['success' => false, 'message' => 'Account not found.'], 404);
|
||||
}
|
||||
|
||||
// Store-level users may only edit their own store's accounts
|
||||
$type = $this->resolveType();
|
||||
$isBig3 = $this->isBig3($type);
|
||||
if (!$isBig3) {
|
||||
if (!$this->guardStore()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
$storeIds = $this->userStoreIds();
|
||||
if (!in_array($account->store_id, $storeIds)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
}
|
||||
|
||||
if ($request->filled('name')) {
|
||||
$account->name = trim((string) $request->input('name'));
|
||||
}
|
||||
if ($request->filled('type')) {
|
||||
$account->type = strtoupper(trim((string) $request->input('type')));
|
||||
}
|
||||
if ($request->filled('default_flow')) {
|
||||
$flow = strtoupper(trim((string) $request->input('default_flow')));
|
||||
if (!in_array($flow, ['INCOME', 'EXPENSE'], true)) {
|
||||
return response()->json(['success' => false, 'message' => 'default_flow must be INCOME or EXPENSE.'], 422);
|
||||
}
|
||||
$account->default_flow = $flow;
|
||||
}
|
||||
if ($request->has('description')) {
|
||||
$account->description = $request->input('description') ?: null;
|
||||
}
|
||||
|
||||
$account->updated_by = Auth::id();
|
||||
$account->save();
|
||||
|
||||
return response()->json(['success' => true, 'data' => $account]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Archive (soft-disable) an account.
|
||||
*/
|
||||
public function archiveAccount(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$account = Account::find($request->input('id'));
|
||||
if (!$account) {
|
||||
return response()->json(['success' => false, 'message' => 'Account not found.'], 404);
|
||||
}
|
||||
|
||||
$type = $this->resolveType();
|
||||
$isBig3 = $this->isBig3($type);
|
||||
if (!$isBig3) {
|
||||
if (!$this->guardStore()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
$storeIds = $this->userStoreIds();
|
||||
if (!in_array($account->store_id, $storeIds)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
}
|
||||
|
||||
if (Account::where('parent_id', $account->id)->where('is_active', true)->exists()) {
|
||||
return response()->json(['success' => false, 'message' => 'Archive child accounts first.'], 422);
|
||||
}
|
||||
if (AccountTransaction::where('account_id', $account->id)->exists()) {
|
||||
return response()->json(['success' => false, 'message' => 'Cannot archive: account has transactions. Move or delete them first.'], 422);
|
||||
}
|
||||
|
||||
$account->is_active = false;
|
||||
$account->updated_by = Auth::id();
|
||||
$account->save();
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an account under a new parent (or to root). Refuses cycles.
|
||||
*/
|
||||
public function moveAccount(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$account = Account::find($request->input('id'));
|
||||
if (!$account) {
|
||||
return response()->json(['success' => false, 'message' => 'Account not found.'], 404);
|
||||
}
|
||||
|
||||
$type = $this->resolveType();
|
||||
$isBig3 = $this->isBig3($type);
|
||||
if (!$isBig3) {
|
||||
if (!$this->guardStore()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
$storeIds = $this->userStoreIds();
|
||||
if (!in_array($account->store_id, $storeIds)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
}
|
||||
|
||||
$newParentId = $request->input('parent_id');
|
||||
$newParentId = ($newParentId === null || $newParentId === '' || $newParentId == 0) ? null : (int)$newParentId;
|
||||
|
||||
if ($newParentId === (int)$account->id) {
|
||||
return response()->json(['success' => false, 'message' => 'An account cannot be its own parent.'], 422);
|
||||
}
|
||||
|
||||
if ($newParentId !== null) {
|
||||
$parent = Account::find($newParentId);
|
||||
if (!$parent) {
|
||||
return response()->json(['success' => false, 'message' => 'Target parent not found.'], 422);
|
||||
}
|
||||
|
||||
$cursor = $parent;
|
||||
$hops = 0;
|
||||
while ($cursor && $hops < 1000) {
|
||||
if ((int)$cursor->id === (int)$account->id) {
|
||||
return response()->json(['success' => false, 'message' => 'Cannot move an account under one of its own descendants.'], 422);
|
||||
}
|
||||
$cursor = $cursor->parent_id ? Account::find($cursor->parent_id) : null;
|
||||
$hops++;
|
||||
}
|
||||
}
|
||||
|
||||
$account->parent_id = $newParentId;
|
||||
$account->updated_by = Auth::id();
|
||||
$account->save();
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-activate an archived account.
|
||||
*/
|
||||
public function restoreAccount(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$account = Account::find($request->input('id'));
|
||||
if (!$account) {
|
||||
return response()->json(['success' => false, 'message' => 'Account not found.'], 404);
|
||||
}
|
||||
|
||||
$type = $this->resolveType();
|
||||
$isBig3 = $this->isBig3($type);
|
||||
if (!$isBig3) {
|
||||
if (!$this->guardStore()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
$storeIds = $this->userStoreIds();
|
||||
if (!in_array($account->store_id, $storeIds)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
}
|
||||
|
||||
$account->is_active = true;
|
||||
$account->updated_by = Auth::id();
|
||||
$account->save();
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme metadata + drift summary for Manage Accounts.
|
||||
*/
|
||||
public function getThemeInfo(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$current = \App\Support\AccountingTheme::current();
|
||||
$definition = \App\Support\AccountingTheme::definition($current);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'current' => $current,
|
||||
'definition' => [
|
||||
'label' => $definition['label'] ?? $current,
|
||||
'description' => $definition['description'] ?? null,
|
||||
'version' => $definition['version'] ?? 1,
|
||||
],
|
||||
'options' => \App\Support\AccountingTheme::options(),
|
||||
'counts' => [
|
||||
'theme_tagged' => Account::where('theme_key', $current)->count(),
|
||||
'user_added' => Account::whereNull('theme_key')->count(),
|
||||
'archived' => Account::where('is_active', false)->count(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read-only drift report.
|
||||
*/
|
||||
public function getThemeDrift(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => \App\Support\AccountingTheme::drift(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the active accounting theme.
|
||||
*/
|
||||
public function setTheme(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$key = trim((string) $request->input('key', ''));
|
||||
if ($key === '' || !\array_key_exists($key, \App\Support\AccountingTheme::all())) {
|
||||
return response()->json(['success' => false, 'message' => 'Unknown theme.'], 422);
|
||||
}
|
||||
|
||||
\App\Models\SystemSetting::setValue('accounting_theme', $key, 'accounting', 'string');
|
||||
|
||||
return response()->json(['success' => true, 'key' => $key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Idempotently re-apply the active theme (additive only).
|
||||
*/
|
||||
public function applyTheme(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
try {
|
||||
$stats = \App\Support\AccountingTheme::apply();
|
||||
} catch (\Throwable $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to apply theme: ' . $e->getMessage(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => sprintf(
|
||||
'Theme "%s" applied: %d created, %d updated, %d unchanged.',
|
||||
$stats['theme_key'],
|
||||
$stats['created'],
|
||||
$stats['stamped'],
|
||||
$stats['skipped']
|
||||
),
|
||||
'data' => $stats,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recent transactions for the Reports page.
|
||||
*/
|
||||
public function listReports(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$type = $this->resolveType();
|
||||
$isBig3 = $this->isBig3($type);
|
||||
|
||||
if (!$isBig3 && !$this->guardStore()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$storeIds = $isBig3 ? [] : $this->userStoreIds();
|
||||
$accountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id');
|
||||
|
||||
$transactions = AccountTransaction::with(['account', 'creator'])
|
||||
->whereIn('account_id', $accountIds)
|
||||
->orderBy('transaction_date', 'desc')
|
||||
->limit(100)
|
||||
->get();
|
||||
|
||||
return response()->json(['success' => true, 'transactions' => $transactions]);
|
||||
}
|
||||
}
|
||||
131
app/Http/Controllers/Admin/ApiTokenController.php
Normal file
131
app/Http/Controllers/Admin/ApiTokenController.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\PersonalAccessToken;
|
||||
use App\Models\User;
|
||||
use App\Support\TokenAbilities;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
|
||||
class ApiTokenController
|
||||
{
|
||||
public function catalog()
|
||||
{
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'abilities' => TokenAbilities::catalog(),
|
||||
'wildcard' => TokenAbilities::WILDCARD,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$tokens = PersonalAccessToken::query()
|
||||
->where('tokenable_type', User::class)
|
||||
->orderByDesc('id')
|
||||
->get()
|
||||
->map(fn ($t) => $this->present($t));
|
||||
|
||||
return response()->json(['success' => true, 'data' => $tokens]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:120',
|
||||
'description' => 'nullable|string|max:500',
|
||||
'abilities' => 'required|array|min:1',
|
||||
'abilities.*' => 'string',
|
||||
'allowed_ips' => 'nullable|array',
|
||||
'allowed_ips.*' => 'string',
|
||||
'expires_at' => 'nullable|date',
|
||||
'tokenable_user_id' => 'nullable|integer',
|
||||
]);
|
||||
|
||||
foreach ($validated['abilities'] as $ability) {
|
||||
if (! TokenAbilities::exists($ability)) {
|
||||
return ResponseHelper::returnError("Unknown ability: {$ability}", 422);
|
||||
}
|
||||
}
|
||||
|
||||
$owner = Auth::user();
|
||||
$tokenable = $owner;
|
||||
if (! empty($validated['tokenable_user_id'])) {
|
||||
$tokenable = User::query()->find($validated['tokenable_user_id']);
|
||||
if (! $tokenable) {
|
||||
return ResponseHelper::returnError('Target user not found.', 404);
|
||||
}
|
||||
}
|
||||
|
||||
$result = $tokenable->createToken(
|
||||
name: $validated['name'],
|
||||
abilities: $validated['abilities'],
|
||||
allowedIps: $validated['allowed_ips'] ?? null,
|
||||
expiresAt: !empty($validated['expires_at']) ? new \DateTimeImmutable($validated['expires_at']) : null,
|
||||
description: $validated['description'] ?? null,
|
||||
createdBy: $owner->id,
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'token' => $this->present($result['token']),
|
||||
'plain_text_token' => $result['plainTextToken'],
|
||||
],
|
||||
'message' => 'Token created. Copy it now — it will not be shown again.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function revoke(Request $request, int $id)
|
||||
{
|
||||
$token = PersonalAccessToken::query()->find($id);
|
||||
if (! $token) {
|
||||
return ResponseHelper::returnError('Token not found.', 404);
|
||||
}
|
||||
if ($token->revoked_at !== null) {
|
||||
return ResponseHelper::returnError('Token already revoked.', 422);
|
||||
}
|
||||
$token->forceFill([
|
||||
'revoked_at' => now(),
|
||||
'revoked_by' => Auth::id(),
|
||||
])->save();
|
||||
|
||||
return response()->json(['success' => true, 'data' => $this->present($token)]);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $id)
|
||||
{
|
||||
$token = PersonalAccessToken::query()->find($id);
|
||||
if (! $token) {
|
||||
return ResponseHelper::returnError('Token not found.', 404);
|
||||
}
|
||||
$token->delete();
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
private function present(PersonalAccessToken $t): array
|
||||
{
|
||||
return [
|
||||
'id' => $t->id,
|
||||
'name' => $t->name,
|
||||
'description' => $t->description,
|
||||
'tokenable_id' => $t->tokenable_id,
|
||||
'tokenable_type' => $t->tokenable_type,
|
||||
'abilities' => $t->abilities ?? [],
|
||||
'allowed_ips' => $t->allowed_ips ?? [],
|
||||
'expires_at' => $t->expires_at,
|
||||
'last_used_at' => $t->last_used_at,
|
||||
'last_used_ip' => $t->last_used_ip,
|
||||
'revoked_at' => $t->revoked_at,
|
||||
'created_by' => $t->created_by,
|
||||
'created_at' => $t->created_at,
|
||||
'is_active' => $t->isActive(),
|
||||
];
|
||||
}
|
||||
}
|
||||
268
app/Http/Controllers/Admin/LandingPageController.php
Normal file
268
app/Http/Controllers/Admin/LandingPageController.php
Normal file
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\LandingPage;
|
||||
use App\Enums\UserTypes;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use Exception;
|
||||
|
||||
class LandingPageController
|
||||
{
|
||||
/**
|
||||
* Allowed roles for landing page management.
|
||||
*/
|
||||
private static array $allowedTypes = [
|
||||
'ult',
|
||||
'super operator',
|
||||
'coordinator',
|
||||
];
|
||||
|
||||
/**
|
||||
* Check if the current user is authorized.
|
||||
*/
|
||||
private function isAuthorized(): bool
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) return false;
|
||||
|
||||
$type = $user->acct_type;
|
||||
if ($type instanceof UserTypes) {
|
||||
$type = $type->value;
|
||||
}
|
||||
|
||||
return in_array($type, self::$allowedTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all landing pages.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (!$this->isAuthorized()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$pages = LandingPage::orderByDesc('is_active')
|
||||
->orderByDesc('updated_at')
|
||||
->get();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $pages,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single landing page by hashkey.
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
if (!$this->isAuthorized()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$hashkey = $request->input('hashkey');
|
||||
if (!$hashkey) {
|
||||
return response()->json(['success' => false, 'message' => 'Hashkey is required'], 400);
|
||||
}
|
||||
|
||||
$page = LandingPage::where('hashkey', $hashkey)->first();
|
||||
if (!$page) {
|
||||
return response()->json(['success' => false, 'message' => 'Landing page not found'], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $page,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a landing page.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (!$this->isAuthorized()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'html_content' => 'required|string',
|
||||
'description' => 'nullable|string|max:1000',
|
||||
'hashkey' => 'nullable|string', // If provided, it's an update
|
||||
]);
|
||||
|
||||
try {
|
||||
$hashkey = $validated['hashkey'] ?? null;
|
||||
|
||||
if ($hashkey) {
|
||||
// Update existing
|
||||
$page = LandingPage::where('hashkey', $hashkey)->first();
|
||||
if (!$page) {
|
||||
return response()->json(['success' => false, 'message' => 'Landing page not found'], 404);
|
||||
}
|
||||
|
||||
$page->title = $validated['title'];
|
||||
$page->html_content = $validated['html_content'];
|
||||
$page->description = $validated['description'] ?? $page->description;
|
||||
$page->save();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Landing page updated successfully',
|
||||
'data' => $page,
|
||||
]);
|
||||
} else {
|
||||
// Create new
|
||||
$page = LandingPage::create([
|
||||
'title' => $validated['title'],
|
||||
'html_content' => $validated['html_content'],
|
||||
'description' => $validated['description'] ?? null,
|
||||
'is_active' => false,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Landing page created successfully',
|
||||
'data' => $page,
|
||||
]);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to save landing page: ' . $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a landing page as active.
|
||||
*/
|
||||
public function setActive(Request $request)
|
||||
{
|
||||
if (!$this->isAuthorized()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$hashkey = $request->input('hashkey');
|
||||
if (!$hashkey) {
|
||||
return response()->json(['success' => false, 'message' => 'Hashkey is required'], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$page = LandingPage::where('hashkey', $hashkey)->first();
|
||||
if (!$page) {
|
||||
return response()->json(['success' => false, 'message' => 'Landing page not found'], 404);
|
||||
}
|
||||
|
||||
$page->setAsActive();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Landing page activated successfully',
|
||||
'data' => $page,
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to activate landing page: ' . $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate all landing pages (show default homepage for guests).
|
||||
*/
|
||||
public function deactivateAll(Request $request)
|
||||
{
|
||||
if (!$this->isAuthorized()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
try {
|
||||
LandingPage::where('is_active', true)->update(['is_active' => false]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'All landing pages deactivated. Default homepage will be shown.',
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to deactivate: ' . $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a landing page.
|
||||
*/
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
if (!$this->isAuthorized()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$hashkey = $request->input('hashkey');
|
||||
if (!$hashkey) {
|
||||
return response()->json(['success' => false, 'message' => 'Hashkey is required'], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$page = LandingPage::where('hashkey', $hashkey)->first();
|
||||
if (!$page) {
|
||||
return response()->json(['success' => false, 'message' => 'Landing page not found'], 404);
|
||||
}
|
||||
|
||||
$page->delete();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Landing page deleted successfully',
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to delete landing page: ' . $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public endpoint: Get the currently active landing page content.
|
||||
* No authentication required - used to show landing page to guests.
|
||||
*/
|
||||
public function getActiveLandingPage()
|
||||
{
|
||||
try {
|
||||
$page = LandingPage::getActive();
|
||||
} catch (\Throwable $e) {
|
||||
return Response::json(['success' => false, 'data' => null, 'has_landing_page' => false]);
|
||||
}
|
||||
|
||||
if (!$page) {
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'data' => null,
|
||||
'has_landing_page' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'title' => $page->title,
|
||||
'html_content' => $page->html_content,
|
||||
'description' => $page->description,
|
||||
],
|
||||
'has_landing_page' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
174
app/Http/Controllers/Admin/PosAccessKeyController.php
Normal file
174
app/Http/Controllers/Admin/PosAccessKeyController.php
Normal file
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\Market\PosAccessKey;
|
||||
use App\Models\Market\Store;
|
||||
use App\Models\User;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hyperf\Stringable\Str;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Enums\UserActions;
|
||||
|
||||
class PosAccessKeyController
|
||||
{
|
||||
/**
|
||||
* List POS access keys. Auto-expires any past-due keys first.
|
||||
* - Ultimate/super operator/operator: see all keys
|
||||
* - Store owner/manager: see keys for stores they own or manage
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewPosAccessKeys)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
// Auto-deactivate any expired keys
|
||||
PosAccessKey::autoExpire();
|
||||
|
||||
$user = Auth::user();
|
||||
$acctType = $user->acct_type->value ?? $user->acct_type ?? '';
|
||||
|
||||
// Ultimate, Super Operator, and Operator see everything
|
||||
if (in_array($acctType, ['ult', 'super operator', 'operator'])) {
|
||||
$query = PosAccessKey::with(['store:id,name,hashkey,owner_id', 'store.owner:id,name,nickname,hashkey'])
|
||||
->orderBy('id', 'desc');
|
||||
} else {
|
||||
// Non-ultimate users see their own and their descendants' stores' keys
|
||||
$descendants = $user->getAllDescendants();
|
||||
$descendantIds = $descendants->pluck('id')->push($user->id)->toArray();
|
||||
|
||||
$storeIds = Store::whereIn('owner_id', $descendantIds)
|
||||
->orWhereIn('manager_id', $descendantIds)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
|
||||
$query = PosAccessKey::with(['store:id,name,hashkey,owner_id', 'store.owner:id,name,nickname,hashkey'])
|
||||
->whereIn('store_id', $storeIds)
|
||||
->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
$keys = $query->get();
|
||||
|
||||
return ResponseHelper::returnSuccessResponse($keys, 'pos_access_keys');
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::CreatePosAccessKey)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'store_hash' => 'required|string',
|
||||
'expires_at' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$store = Store::where('hashkey', $validated['store_hash'])->first();
|
||||
if (!$store) {
|
||||
return ResponseHelper::returnError('Store not found', 404);
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
$acctType = $user->acct_type->value ?? $user->acct_type ?? '';
|
||||
if (!in_array($acctType, ['ult', 'super operator', 'operator'])) {
|
||||
$descendants = $user->getAllDescendants();
|
||||
$allowedIds = $descendants->pluck('id')->push($user->id)->toArray();
|
||||
if (!in_array($store->owner_id, $allowedIds) && !in_array($store->manager_id, $allowedIds)) {
|
||||
return ResponseHelper::returnError('Unauthorized to create keys for this store', 403);
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'access_key' => 'PK-' . Str::upper(Str::random(16)),
|
||||
'store_id' => $store->id,
|
||||
'name' => $validated['name'],
|
||||
'created_by' => Auth::id(),
|
||||
'status' => 'active',
|
||||
];
|
||||
|
||||
// Set expiry if provided
|
||||
if (!empty($validated['expires_at'])) {
|
||||
$data['expires_at'] = $validated['expires_at'];
|
||||
}
|
||||
|
||||
$key = PosAccessKey::create($data);
|
||||
|
||||
return ResponseHelper::returnSuccessResponse($key, $key->hashkey, 'POS Access Key created');
|
||||
}
|
||||
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::DeletePosAccessKey)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$hashkey = $request->input('target');
|
||||
$key = PosAccessKey::with('store')->where('hashkey', $hashkey)->first();
|
||||
|
||||
if (!$key) {
|
||||
return ResponseHelper::returnError('Key not found', 404);
|
||||
}
|
||||
|
||||
if (!$this->userOwnsKeyStore($key)) {
|
||||
return ResponseHelper::returnError('Unauthorized to delete this key', 403);
|
||||
}
|
||||
|
||||
$key->delete();
|
||||
return ResponseHelper::returnSuccessResponse([], $hashkey, 'Key deleted');
|
||||
}
|
||||
|
||||
public function toggleStatus(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::TogglePosAccessKey)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$hashkey = $request->input('target');
|
||||
$key = PosAccessKey::with('store')->where('hashkey', $hashkey)->first();
|
||||
|
||||
if (!$key) {
|
||||
return ResponseHelper::returnError('Key not found', 404);
|
||||
}
|
||||
|
||||
if (!$this->userOwnsKeyStore($key)) {
|
||||
return ResponseHelper::returnError('Unauthorized to modify this key', 403);
|
||||
}
|
||||
|
||||
$key->status = $key->status === 'active' ? 'inactive' : 'active';
|
||||
$key->save();
|
||||
return ResponseHelper::returnSuccessResponse($key, $hashkey, 'Status updated');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ownership gate shared by destroy/toggleStatus: Big 3 always pass;
|
||||
* everyone else must own or manage (directly or via descendants) the
|
||||
* store the key belongs to.
|
||||
*/
|
||||
private function userOwnsKeyStore(PosAccessKey $key): bool
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$acctType = $user->acct_type->value ?? $user->acct_type ?? '';
|
||||
if (in_array($acctType, ['ult', 'super operator', 'operator'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$store = $key->store;
|
||||
if (!$store) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$allowedIds = $user->getAllDescendants()->pluck('id')->push($user->id)->toArray();
|
||||
return in_array($store->owner_id, $allowedIds) || in_array($store->manager_id, $allowedIds);
|
||||
}
|
||||
}
|
||||
|
||||
284
app/Http/Controllers/Admin/SystemSettingsController.php
Normal file
284
app/Http/Controllers/Admin/SystemSettingsController.php
Normal file
@@ -0,0 +1,284 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\FilesMainController;
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\SystemSetting;
|
||||
use App\Enums\UserTypes;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Exception;
|
||||
|
||||
class SystemSettingsController
|
||||
{
|
||||
/**
|
||||
* Get all system settings grouped by group.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if (!Auth::user()->isUltimate()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$keys = $request->input('keys', []);
|
||||
|
||||
if (!empty($keys)) {
|
||||
$settings = SystemSetting::whereIn('key', (array)$keys)->get();
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $settings
|
||||
]);
|
||||
}
|
||||
|
||||
$settings = SystemSetting::all()->groupBy('group');
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $settings
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update multiple settings.
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
if (!Auth::user()->isUltimate()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'settings' => 'required|array',
|
||||
]);
|
||||
|
||||
try {
|
||||
foreach ($validated['settings'] as $key => $value) {
|
||||
SystemSetting::setValue($key, $value);
|
||||
}
|
||||
|
||||
SystemSetting::clearCache();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'System settings updated successfully'
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to update settings: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload application logo.
|
||||
*/
|
||||
public function uploadLogo(Request $request)
|
||||
{
|
||||
if (!Auth::user()->isUltimate()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
if (!$request->hasFile('logo')) {
|
||||
return response()->json(['success' => false, 'message' => 'No logo file provided'], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$file = $request->file('logo');
|
||||
$filename = 'app_logo_' . time() . '.' . $file->getExtension();
|
||||
|
||||
// Extract palette from the uploaded file before it is moved by storage.
|
||||
$palette = \App\Support\PaletteExtractor::extract($file->getRealPath());
|
||||
|
||||
// Reusing existing file storage system
|
||||
$result = FilesMainController::uploadFileList(
|
||||
$file,
|
||||
'System Logo',
|
||||
$filename,
|
||||
'Application branding logo',
|
||||
[],
|
||||
'system',
|
||||
[],
|
||||
0,
|
||||
'app_logo',
|
||||
);
|
||||
|
||||
if ($result && isset($result->hashkey)) {
|
||||
SystemSetting::setValue('app_logo', $result->hashkey);
|
||||
|
||||
if ($palette) {
|
||||
SystemSetting::setValue('primary_color', $palette['primary']);
|
||||
SystemSetting::setValue('accent_color', $palette['accent']);
|
||||
SystemSetting::setValue('background_tint', $palette['tint']);
|
||||
}
|
||||
|
||||
SystemSetting::clearCache();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'hashkey' => $result->hashkey,
|
||||
'url' => FilesMainController::generateURLforFileListHash($result->hashkey),
|
||||
'palette' => $palette,
|
||||
'message' => 'Logo uploaded successfully'
|
||||
]);
|
||||
}
|
||||
|
||||
throw new Exception('File upload failed');
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Logo upload failed: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get public settings data.
|
||||
*/
|
||||
public static function getPublicSettingsData()
|
||||
{
|
||||
$publicKeys = ['app_name', 'app_logo', 'app_description', 'app_tagline', 'primary_color', 'accent_color', 'background_tint', 'footer_text', 'disabled_pages', 'top_up_enabled', 'app_mode', 'main_organization', 'accounting_theme', 'default_org_type', 'group_types', 'bible_verse_text', 'bible_verse_reference'];
|
||||
|
||||
$settings = [];
|
||||
foreach ($publicKeys as $key) {
|
||||
$settings[$key] = SystemSetting::getValue($key);
|
||||
}
|
||||
|
||||
// Add module states
|
||||
$settings['module_states'] = \App\Support\ModuleHelper::getModuleStates();
|
||||
|
||||
// Generate URL for logo if it exists
|
||||
if (!empty($settings['app_logo'])) {
|
||||
$settings['app_logo_url'] = FilesMainController::generateURLforFileListHash($settings['app_logo']);
|
||||
} else {
|
||||
$settings['app_logo_url'] = cdn_asset('vendor/assets/icons/192x192.png'); // Fallback to PWA icon on the CDN
|
||||
}
|
||||
|
||||
// Resolve main organization hashkey to a usable data object
|
||||
$settings['main_organization_data'] = null;
|
||||
if (!empty($settings['main_organization'])) {
|
||||
$org = \App\Models\Market\Organization::where('hashkey', $settings['main_organization'])->first();
|
||||
if ($org) {
|
||||
$settings['main_organization_data'] = [
|
||||
'hashkey' => $org->hashkey,
|
||||
'name' => $org->name,
|
||||
'type' => $org->type,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* List organizations available for selection as the main cooperative/organization.
|
||||
*/
|
||||
public function listOrganizations()
|
||||
{
|
||||
if (!Auth::user()->isUltimate()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$orgs = \App\Models\Market\Organization::where('is_active', true)
|
||||
->orderBy('type')
|
||||
->orderBy('name')
|
||||
->get(['hashkey', 'name', 'type']);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $orgs,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all modules with their effective state, env/config default, and
|
||||
* any DB-stored override. Used by the Ultimate Console module manager.
|
||||
*/
|
||||
public function getModules()
|
||||
{
|
||||
if (!Auth::user()->isUltimate()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'system_enabled' => \App\Support\ModuleHelper::isSystemEnabled(),
|
||||
'modules' => \App\Support\ModuleHelper::getAllModules(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the module override map. Accepts an associative array of
|
||||
* { module_key: bool|null }. A null value clears that key's override
|
||||
* so the env/config default takes effect again.
|
||||
*/
|
||||
public function updateModules(Request $request)
|
||||
{
|
||||
if (!Auth::user()->isUltimate()) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'overrides' => 'required|array',
|
||||
]);
|
||||
|
||||
$configModules = (array) \Hypervel\Support\Facades\Config::get('modules', []);
|
||||
$current = SystemSetting::getValue(\App\Support\ModuleHelper::OVERRIDE_KEY);
|
||||
if (is_string($current) && $current !== '') {
|
||||
$decoded = json_decode($current, true);
|
||||
$current = is_array($decoded) ? $decoded : [];
|
||||
} elseif (!is_array($current)) {
|
||||
$current = [];
|
||||
}
|
||||
|
||||
foreach ($validated['overrides'] as $key => $value) {
|
||||
// Only allow keys that are actually defined as modules.
|
||||
if (!isset($configModules[$key]) || !is_array($configModules[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($value === null || $value === 'null') {
|
||||
unset($current[$key]);
|
||||
} else {
|
||||
$current[$key] = filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
SystemSetting::setValue(
|
||||
\App\Support\ModuleHelper::OVERRIDE_KEY,
|
||||
json_encode((object) $current),
|
||||
'modules',
|
||||
'json'
|
||||
);
|
||||
SystemSetting::clearCache();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Module states updated',
|
||||
'data' => [
|
||||
'modules' => \App\Support\ModuleHelper::getAllModules(),
|
||||
],
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to update modules: ' . $e->getMessage(),
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get public settings for frontend.
|
||||
*/
|
||||
public function getPublicSettings()
|
||||
{
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => self::getPublicSettingsData()
|
||||
]);
|
||||
}
|
||||
}
|
||||
412
app/Http/Controllers/FilesMainController.php
Normal file
412
app/Http/Controllers/FilesMainController.php
Normal file
@@ -0,0 +1,412 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Hypervel\Http\Request;
|
||||
use App\Models\FileList;
|
||||
use App\Models\FileContent;
|
||||
use Hypervel\Http\UploadedFile;
|
||||
use Hypervel\Support\Facades\File;
|
||||
use Hypervel\Support\Facades\Storage;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\DB;
|
||||
use \PDO;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use Hypervel\Support\Carbon;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Http\Controllers\Helpers\Permissions\ProductPermissions;
|
||||
use App\Enums\UserActions;
|
||||
|
||||
class FilesMainController
|
||||
{
|
||||
|
||||
private static function isLikelyBinary($string, $threshold = 0.3)
|
||||
{
|
||||
// Null byte check: very likely binary
|
||||
if (strpos($string, "\x00") !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If string is empty, consider it not binary
|
||||
$len = strlen($string);
|
||||
if ($len === 0)
|
||||
return false;
|
||||
|
||||
// Count non-printable characters
|
||||
$nonPrintable = preg_match_all('~[^\x09\x0A\x0D\x20-\x7E]~', $string);
|
||||
|
||||
// If more than $threshold of the content is non-printable, likely binary
|
||||
return ($nonPrintable / $len) > $threshold;
|
||||
}
|
||||
|
||||
private static function insertFileContentPostgresPDO(array $data)
|
||||
{
|
||||
$pdo = DB::getPdo();
|
||||
|
||||
$stmt = $pdo->prepare('
|
||||
INSERT INTO file_content
|
||||
(hashkey, filehash, titlename, description, size_in_bytes, content, filelocation, created_by, updated_by, details, created_at, updated_at)
|
||||
VALUES
|
||||
(:hashkey, :filehash, :titlename, :description, :size_in_bytes, :content, :filelocation, :created_by, :updated_by, :details, :created_at, :updated_at)
|
||||
');
|
||||
|
||||
$now = now()->toDateTimeString();
|
||||
|
||||
$stmt->bindParam(':hashkey', $data['hashkey']);
|
||||
$stmt->bindParam(':filehash', $data['filehash']);
|
||||
$stmt->bindParam(':titlename', $data['titlename']);
|
||||
$stmt->bindParam(':description', $data['description']);
|
||||
$stmt->bindParam(':size_in_bytes', $data['size_in_bytes']);
|
||||
$stmt->bindParam(':content', $data['content'], PDO::PARAM_LOB);
|
||||
$stmt->bindParam(':filelocation', $data['filelocation']);
|
||||
$stmt->bindParam(':created_by', $data['created_by']);
|
||||
$stmt->bindParam(':updated_by', $data['updated_by']);
|
||||
$detailsJson = json_encode($data['details']);
|
||||
$stmt->bindParam(':details', $detailsJson);
|
||||
$stmt->bindParam(':created_at', $now);
|
||||
$stmt->bindParam(':updated_at', $now);
|
||||
|
||||
$stmt->execute();
|
||||
|
||||
// Optionally, get the inserted ID and fetch the model
|
||||
$id = DB::getPdo()->lastInsertId();
|
||||
|
||||
return FileContent::find($id);
|
||||
}
|
||||
|
||||
private static function insertFileContentSql(array $data)
|
||||
{
|
||||
$now = Carbon::now();
|
||||
|
||||
$data['content'] = base64_encode($data['content']);
|
||||
$data['details'] = json_encode($data['details']);
|
||||
$data['created_at'] = $now;
|
||||
$data['updated_at'] = $now;
|
||||
|
||||
return FileContent::create($data);
|
||||
}
|
||||
|
||||
|
||||
private static function insertFileContent(array $data)
|
||||
{
|
||||
$driver = DB::connection()->getDriverName();
|
||||
|
||||
if ($driver === 'pgsql') {
|
||||
return self::insertFileContentPostgresPDO($data);
|
||||
}
|
||||
|
||||
if ($driver === 'mysql') {
|
||||
return self::insertFileContentSql($data);
|
||||
}
|
||||
|
||||
throw new \RuntimeException("Unsupported database driver: {$driver}");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Upload file content and store metadata in DB.
|
||||
*
|
||||
* @param \Hypervel\Http\UploadedFile|string $fileData // filepath, filebinaryData, UploadedFileRequest
|
||||
* @param string $title
|
||||
* @param string|null $description
|
||||
* @param string|null $filelocation
|
||||
* @param array|null $details
|
||||
*/
|
||||
private static function uploadFileContent(UploadedFile|string $fileData, string $title, ?string $description = null, ?array $details = [])
|
||||
{
|
||||
|
||||
|
||||
if ($fileData instanceof UploadedFile) {
|
||||
$fileHash = hash_file('sha256', $fileData->getRealPath());
|
||||
$fileSize = $fileData->getSize();
|
||||
$filename = $fileHash;
|
||||
$fileContent = File::get($fileData->getRealPath());
|
||||
$path = $fileData->storeAs('files', $filename);
|
||||
} elseif (is_string($fileData) && file_exists($fileData)) {
|
||||
|
||||
$fileHash = hash_file('sha256', $fileData);
|
||||
$fileSize = filesize($fileData);
|
||||
$fileContent = file_get_contents($fileData);
|
||||
$path = Storage::put("files/{$fileHash}", $fileContent);
|
||||
|
||||
} elseif (self::isLikelyBinary($fileData)) {
|
||||
$fileHash = hash('sha256', $fileData);
|
||||
$fileSize = strlen($fileData);
|
||||
// $path = Storage::putFile('files', $fileData);
|
||||
$fileContent = $fileData;
|
||||
$path = Storage::put("files/{$fileHash}", $fileContent);
|
||||
} else {
|
||||
throw new \InvalidArgumentException('Invalid file data provided.');
|
||||
}
|
||||
|
||||
$hashKey = hash('sha256', uniqid((string) now(), true));
|
||||
|
||||
|
||||
$finfo = new \finfo(FILEINFO_MIME_TYPE);
|
||||
$mimetype = $finfo->buffer($fileContent);
|
||||
|
||||
// $fileContent = FileContent::create([
|
||||
// 'hashkey' => $hashKey,
|
||||
// 'filehash' => $fileHash,
|
||||
// 'titlename' => $title,
|
||||
// 'description' => $description,
|
||||
// 'size_in_bytes' => $fileSize,
|
||||
// 'content' => $fileContent,
|
||||
// 'filelocation' => $filelocation ?? $path,
|
||||
// 'created_by' => Auth::id(),
|
||||
// 'updated_by' => Auth::id(),
|
||||
// 'details' => $details ?? [],
|
||||
// ]);
|
||||
|
||||
$fileContentDB = FileContent::where('filehash', $fileHash)->first();
|
||||
|
||||
if ($fileContentDB) {
|
||||
return $fileContentDB;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
$fileContentDB = self::insertFileContent([
|
||||
'hashkey' => $hashKey,
|
||||
'filehash' => $fileHash,
|
||||
'titlename' => $title,
|
||||
'description' => $description,
|
||||
'size_in_bytes' => $fileSize,
|
||||
'content' => $fileContent,
|
||||
'filelocation' => $filelocation ?? $path,
|
||||
'created_by' => Auth::id(),
|
||||
'updated_by' => Auth::id(),
|
||||
'details' => $details ?? [],
|
||||
'mimetype' => $mimetype
|
||||
]);
|
||||
|
||||
return $fileContentDB;
|
||||
}
|
||||
|
||||
|
||||
private static function insertFileList(int $contentuid, string $title, string $filename, string $description, $categories, $details, $tags = [], $hidden = 0, ?string $file_type = null)
|
||||
{
|
||||
$filecontent_exists = FileContent::where('id', $contentuid)->exists();
|
||||
if (!$filecontent_exists) {
|
||||
throw new \Exception("File Content Does not Exist", 1);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'contentuid' => $contentuid,
|
||||
'hashkey' => hash('sha256', uniqid((string) now(), true)),
|
||||
'title' => $title,
|
||||
'filename' => $filename,
|
||||
'description' => $description,
|
||||
'categories' => $categories,
|
||||
'details' => $details,
|
||||
'tags' => $tags,
|
||||
'hidden' => $hidden,
|
||||
'file_type' => $file_type,
|
||||
'is_public' => false,
|
||||
'created_by' => Auth::id(),
|
||||
'updated_by' => Auth::id(),
|
||||
];
|
||||
$filelist = FileList::create($data);
|
||||
|
||||
|
||||
|
||||
return $filelist;
|
||||
|
||||
}
|
||||
|
||||
public static function uploadFileList(string|UploadedFile $fileData, string $title, string $filename, ?string $description = null, ?array $details = [], $categories = null, $tags = [], $hidden = 0, ?string $file_type = null)
|
||||
{
|
||||
try {
|
||||
$fileContent = self::uploadFileContent($fileData, $title, $description, $details);
|
||||
$filecontent_id = $fileContent->id;
|
||||
// print_r($fileContent);
|
||||
return self::insertFileList($filecontent_id, $title, $filename, $description, $categories, $details, $tags, $hidden, $file_type);
|
||||
} catch (\Throwable $th) {
|
||||
return Response::json($th->getMessage(), 500);
|
||||
// return Response::make('Error uploading file content: ' . $th->getMessage(), 500);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static function viewFilebyFileListHash(string $filelist_hash)
|
||||
{
|
||||
$filelist = FileList::where('hashkey', $filelist_hash)->first();
|
||||
|
||||
// return Response::json($filelist, 500);
|
||||
|
||||
if (!$filelist) {
|
||||
abort(404, 'File not found');
|
||||
}
|
||||
|
||||
$cdnUrl = trim((string) ($filelist->cdn_url ?? ''));
|
||||
if ($cdnUrl !== '') {
|
||||
return redirect($cdnUrl);
|
||||
}
|
||||
|
||||
$fileContent = $filelist->fileContent;
|
||||
|
||||
if (!$fileContent) {
|
||||
abort(404, 'File content not found');
|
||||
}
|
||||
|
||||
$content = $fileContent->content;
|
||||
|
||||
$driver = DB::connection()->getDriverName();
|
||||
|
||||
if (is_resource($content) && $driver !== 'mysql') {
|
||||
$content = stream_get_contents($content);
|
||||
}
|
||||
|
||||
if ($driver === 'mysql') {
|
||||
$content = base64_decode($content);
|
||||
}
|
||||
|
||||
|
||||
$finfo = new \finfo(FILEINFO_MIME_TYPE);
|
||||
$mimeType = $finfo->buffer($content);
|
||||
|
||||
// Map common MIME types to extensions
|
||||
$mimeMap = [
|
||||
'image/jpeg' => 'jpg',
|
||||
'image/png' => 'png',
|
||||
'image/gif' => 'gif',
|
||||
'image/webp' => 'webp',
|
||||
'application/pdf' => 'pdf',
|
||||
'text/plain' => 'txt',
|
||||
'text/html' => 'html',
|
||||
'application/zip' => 'zip',
|
||||
'application/json' => 'json',
|
||||
'audio/mpeg' => 'mp3',
|
||||
'video/mp4' => 'mp4',
|
||||
// add more as needed
|
||||
];
|
||||
|
||||
// Pick extension from map, or fallback
|
||||
$extension = $mimeMap[$mimeType] ?? pathinfo($filelist->filename, PATHINFO_EXTENSION);
|
||||
|
||||
// Build safe filename with extension
|
||||
$filename = pathinfo($filelist->filename, PATHINFO_FILENAME) . '.' . $extension;
|
||||
|
||||
return Response::make($content, 200, [
|
||||
'Content-Type' => $mimeType,
|
||||
'Content-Disposition' => 'inline; filename="' . $filename . '"',
|
||||
'Content-Length' => strlen($content),
|
||||
]);
|
||||
|
||||
// return Response::make($content, 200, [
|
||||
// 'Content-Type' => 'application/octet-stream',
|
||||
// 'Content-Disposition' => 'inline; filename="' . $filelist->filename . '"',
|
||||
// 'Content-Length' => strlen($content),
|
||||
// ]);
|
||||
}
|
||||
|
||||
private static function canUploadCategory(string $category): bool
|
||||
{
|
||||
if (!Auth::check()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::UploadAllFiles)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcasecmp($category, 'ProductMarket') === 0) {
|
||||
return ProductPermissions::isActionAllowed(UserActions::CreateProductForOwnStore)
|
||||
|| ProductPermissions::isActionAllowed(UserActions::AddProducttoOwnStore);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function UploadFilefromRequest(Request $request, string $category)
|
||||
{
|
||||
if (!self::canUploadCategory($category)) {
|
||||
return response()->json(['error' => 'Unauthorized'], 403);
|
||||
}
|
||||
|
||||
if (!$request->hasFile('file')) {
|
||||
// $responseData =['success' => false, 'message' => 'No file uploaded'];
|
||||
|
||||
$responseData = false;
|
||||
return response()->json(false, 400);
|
||||
}
|
||||
|
||||
|
||||
$file = $request->file('file');
|
||||
$filename = $file->getClientFilename();
|
||||
$result = self::uploadFileList(
|
||||
$file,
|
||||
'',
|
||||
$filename ?? '',
|
||||
'',
|
||||
[],
|
||||
$category,
|
||||
[],
|
||||
0,
|
||||
null,
|
||||
);
|
||||
|
||||
// return response()->json(['success' => false, 'error' => 'File upload failed'], 500);
|
||||
|
||||
// return $result;
|
||||
$file_url = $result->resolvedUrl();
|
||||
|
||||
try {
|
||||
|
||||
if (is_string($result->hashkey) && !empty($result->hashkey)) {
|
||||
return response()->json(['success' => true, 'hashkey' => $result->hashkey, 'message' => 'File uploaded successfully', 'url' => $file_url, 'name' => $filename], 200);
|
||||
} else {
|
||||
throw new \Exception("File upload failed", 1);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
return response()->json(['success' => false, 'error' => 'File upload failed'], 500);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static function generateURLforFileListHash(string $filelist_hashkey)
|
||||
{
|
||||
$cdnUrl = FileList::where('hashkey', $filelist_hashkey)->value('cdn_url');
|
||||
if (is_string($cdnUrl) && trim($cdnUrl) !== '') {
|
||||
return $cdnUrl;
|
||||
}
|
||||
return "/RequestData/File/$filelist_hashkey";
|
||||
// return route('requestdata.file.view', ['hash' => $filelist_hashkey]);
|
||||
}
|
||||
|
||||
public static function bladePreloadFileScript(string|FileList $FileHash): null|string
|
||||
{
|
||||
try {
|
||||
if ($FileHash instanceof FileList) {
|
||||
|
||||
$filecontent = $FileHash->fileContent->content;
|
||||
|
||||
$mimeType = $FileHash->fileContent->mimetype ?? 'application/octet-stream';
|
||||
$FileHash = $FileHash->hashkey;
|
||||
} else {
|
||||
$FileHash = FileList::where('hashkey', $FileHash)->firstOrFail();
|
||||
$filecontent = $FileHash->fileContent->content;
|
||||
$mimeType = $FileHash->fileContent->mimetype;
|
||||
$FileHash = $FileHash->hashkey;
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
$base64 = base64_encode($filecontent);
|
||||
|
||||
$htmlString = "reqcacheupdateBase64toFile('$FileHash','$base64','$mimeType')";
|
||||
return $htmlString;
|
||||
}
|
||||
|
||||
}
|
||||
177
app/Http/Controllers/GlobalTransactionController.php
Normal file
177
app/Http/Controllers/GlobalTransactionController.php
Normal file
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\Market\ProductTransactionType;
|
||||
use App\Enums\Market\TransactionFlow;
|
||||
use App\Enums\UserTypes;
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\GlobalTransaction;
|
||||
use App\Models\Market\Product;
|
||||
use App\Models\Market\Store;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\Validator;
|
||||
use App\Http\Controllers\Helpers\UserController;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Enums\UserActions;
|
||||
|
||||
class GlobalTransactionController
|
||||
{
|
||||
/**
|
||||
* List transactions with optional filtering.
|
||||
* Handles POST /admin/transactions/list
|
||||
*/
|
||||
public function list(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewGlobalTransactions)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
|
||||
$productIdHash = $request->input('product_id');
|
||||
$storeIdHash = $request->input('store_id');
|
||||
|
||||
$query = GlobalTransaction::with(['user:id,name,hashkey', 'product:id,name,hashkey', 'store:id,name,hashkey']);
|
||||
|
||||
if ($productIdHash) {
|
||||
$product = Product::where('hashkey', $productIdHash)->first();
|
||||
if ($product) {
|
||||
$query->where('product_id', $product->id);
|
||||
} else {
|
||||
return response()->json([]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($storeIdHash) {
|
||||
$store = Store::where('hashkey', $storeIdHash)->first();
|
||||
if ($store) {
|
||||
$query->where('store_id', $store->id);
|
||||
}
|
||||
}
|
||||
|
||||
// Access Control: Ultimate can see everything.
|
||||
// Others see transactions they created or relate to them.
|
||||
if ($user->acct_type !== UserTypes::ULTIMATE) {
|
||||
$query->where(function($q) use ($user) {
|
||||
$q->where('user_id', $user->id)
|
||||
->orWhere('created_by', $user->id);
|
||||
});
|
||||
}
|
||||
|
||||
$transactions = $query->orderBy('created_at', 'desc')->get();
|
||||
|
||||
// Transform for frontend
|
||||
$transformed = $transactions->map(function ($tx) {
|
||||
return [
|
||||
'id' => $tx->id,
|
||||
'hashkey' => $tx->hashkey,
|
||||
'amount' => $tx->amount,
|
||||
'type' => $tx->type ? $tx->type->label() : 'Unknown',
|
||||
'status' => $tx->status,
|
||||
'description' => $tx->description,
|
||||
'created_at' => $tx->created_at,
|
||||
'flow' => $tx->flow ? [
|
||||
'value' => $tx->flow->value,
|
||||
'label' => match($tx->flow) {
|
||||
TransactionFlow::INCOME => 'Income',
|
||||
TransactionFlow::EXPENSE => 'Expense',
|
||||
TransactionFlow::NEUTRAL => 'Neutral',
|
||||
default => 'Unknown'
|
||||
}
|
||||
] : null,
|
||||
'user' => $tx->user ? $tx->user->name : null,
|
||||
'product' => $tx->product ? $tx->product->name : null,
|
||||
'store' => $tx->store ? $tx->store->name : null,
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json($transformed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new global transaction.
|
||||
* Handles POST /admin/transactions/create
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::CreateGlobalTransaction)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'user_hash' => 'nullable|string',
|
||||
'amount' => 'required|numeric',
|
||||
'type' => 'required|integer',
|
||||
'description' => 'nullable|string',
|
||||
'product_hash' => 'nullable|string',
|
||||
'store_hash' => 'nullable|string',
|
||||
'status' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json(['success' => false, 'errors' => $validator->errors()], 422);
|
||||
}
|
||||
|
||||
$data = $validator->validated();
|
||||
|
||||
// Resolve IDs from hashes
|
||||
$targetUserId = $user->id;
|
||||
if (isset($data['user_hash'])) {
|
||||
$targetUser = UserController::findUserIdByHash($data['user_hash']);
|
||||
if ($targetUser) $targetUserId = $targetUser;
|
||||
}
|
||||
|
||||
$productId = null;
|
||||
if (isset($data['product_hash'])) {
|
||||
$product = Product::where('hashkey', $data['product_hash'])->first();
|
||||
if ($product) $productId = $product->id;
|
||||
}
|
||||
|
||||
$storeId = null;
|
||||
if (isset($data['store_hash'])) {
|
||||
$store = Store::where('hashkey', $data['store_hash'])->first();
|
||||
if ($store) $storeId = $store->id;
|
||||
}
|
||||
|
||||
/** @var GlobalTransaction $transaction */
|
||||
$transaction = GlobalTransaction::create([
|
||||
'user_id' => $targetUserId,
|
||||
'amount' => $data['amount'],
|
||||
'type' => $data['type'],
|
||||
'description' => $data['description'] ?? '',
|
||||
'product_id' => $productId,
|
||||
'store_id' => $storeId,
|
||||
'status' => $data['status'] ?? 'completed',
|
||||
'flow' => $request->has('flow') ? $request->input('flow') : ProductTransactionType::from($data['type'])->flow(),
|
||||
'created_by' => $user->id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Transaction recorded successfully',
|
||||
'hashkey' => $transaction->hashkey
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all product transaction types for dropdown selection.
|
||||
*/
|
||||
public function getTypes()
|
||||
{
|
||||
$types = collect(ProductTransactionType::cases())->map(function ($type) {
|
||||
return [
|
||||
'value' => $type->value,
|
||||
'label' => $type->label(),
|
||||
'flow' => $type->flow()->value,
|
||||
'flow_label' => $type->flow()->name
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json($types);
|
||||
}
|
||||
}
|
||||
127
app/Http/Controllers/Helpers/CacheHelper.php
Normal file
127
app/Http/Controllers/Helpers/CacheHelper.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Helpers;
|
||||
|
||||
use Hyperf\Coroutine\Coroutine;
|
||||
use Hypervel\Support\Facades\Cache;
|
||||
use Hyperf\Database\Model\Builder as ModelBuilder;
|
||||
use Hyperf\Database\Query\Builder as QueryBuilder;
|
||||
use Hypervel\Database\Eloquent\Model;
|
||||
|
||||
class CacheHelper
|
||||
{
|
||||
/**
|
||||
* Get data from cache.
|
||||
*
|
||||
* @param mixed $query Can be SQL string or Builder object
|
||||
* @param mixed $params Parameters to hash (array, object, or model)
|
||||
* @return mixed|null
|
||||
*/
|
||||
public static function get_cache($query, $params = [])
|
||||
{
|
||||
try {
|
||||
$key = self::generateKey($query, $params);
|
||||
return Cache::get($key);
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data in cache using a background coroutine (fire-and-forget).
|
||||
*
|
||||
* @param mixed $query Can be SQL string or Builder object
|
||||
* @param mixed $params Parameters to hash
|
||||
* @param mixed $data Data to cache
|
||||
* @param int $ttl Time to live in seconds (default: 1 day)
|
||||
*/
|
||||
public static function set_cache($query, $data, $params = [], int $ttl = 86400)
|
||||
{
|
||||
$key = self::generateKey($query, $params);
|
||||
|
||||
// Fire-and-forget coroutine: we don't wait for completion
|
||||
Coroutine::create(function () use ($key, $data, $ttl) {
|
||||
try {
|
||||
Cache::put($key, $data, $ttl);
|
||||
} catch (\Throwable $e) {
|
||||
// Silently fail as cache operations are non-critical
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Erase a specific cache entry.
|
||||
*
|
||||
* @param mixed $query Can be SQL string or Builder object
|
||||
* @param mixed $params Parameters to hash
|
||||
*/
|
||||
public static function erase_cache($query, $params = [])
|
||||
{
|
||||
try {
|
||||
$key = self::generateKey($query, $params);
|
||||
Cache::forget($key);
|
||||
} catch (\Throwable $e) {
|
||||
// Silently fail
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the cache key using the specific format:
|
||||
* querycache:[queryhash]++++[parameterhash]
|
||||
*/
|
||||
public static function generateKey($query, $params): string
|
||||
{
|
||||
$sql = '';
|
||||
$parameterData = $params;
|
||||
|
||||
// Automatically handle Builder objects
|
||||
if ($query instanceof ModelBuilder || $query instanceof QueryBuilder) {
|
||||
$sql = $query->toSql();
|
||||
// If explicit params were not provided or empty, use the builder's bindings
|
||||
if (empty($params)) {
|
||||
$parameterData = $query->getBindings();
|
||||
}
|
||||
} else {
|
||||
$sql = (string) $query;
|
||||
}
|
||||
|
||||
$queryHash = md5($sql);
|
||||
$paramHash = md5(self::serializeParams($parameterData));
|
||||
|
||||
return "querycache:{$queryHash}++++{$paramHash}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep serialization of parameters for hashing.
|
||||
* Supports arrays, objects, and models.
|
||||
*/
|
||||
private static function serializeParams($params): string
|
||||
{
|
||||
if (is_array($params)) {
|
||||
$processed = [];
|
||||
foreach ($params as $key => $value) {
|
||||
$processed[$key] = self::serializeParams($value);
|
||||
}
|
||||
// Sort keys to ensure consistent hashing for the same associative array
|
||||
ksort($processed);
|
||||
return json_encode($processed);
|
||||
}
|
||||
|
||||
if (is_object($params)) {
|
||||
// Handle models specifically
|
||||
if ($params instanceof Model) {
|
||||
return get_class($params) . ':' . ($params->hashkey ?? $params->id ?? 'new');
|
||||
}
|
||||
|
||||
if (method_exists($params, 'toArray')) {
|
||||
return json_encode($params->toArray());
|
||||
}
|
||||
|
||||
return get_class($params) . ':' . spl_object_hash($params);
|
||||
}
|
||||
|
||||
return (string) $params;
|
||||
}
|
||||
}
|
||||
2541
app/Http/Controllers/Helpers/Legacy/Backup/DB.php
Normal file
2541
app/Http/Controllers/Helpers/Legacy/Backup/DB.php
Normal file
File diff suppressed because it is too large
Load Diff
304
app/Http/Controllers/Helpers/Legacy/Backup/DBEXT/FILESDB.php
Normal file
304
app/Http/Controllers/Helpers/Legacy/Backup/DBEXT/FILESDB.php
Normal file
@@ -0,0 +1,304 @@
|
||||
<?php
|
||||
//File Content
|
||||
|
||||
class DB_FILE_CONTENT
|
||||
{
|
||||
|
||||
public $DB = false;
|
||||
public $tablename = 'file_content';
|
||||
|
||||
use BASICDB;
|
||||
public function __construct($DB = false)
|
||||
{
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
if (!$DB) {
|
||||
return false;
|
||||
}
|
||||
if ($DB) {
|
||||
$this->DB = $DB;
|
||||
}
|
||||
if (!$this->tablename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
function NewFileContent($file, $filename = '', $toDB = true)
|
||||
{
|
||||
if (!file_exists($file)) {
|
||||
return false;
|
||||
}
|
||||
if (!$filename) {
|
||||
$filename = basename($file);
|
||||
}
|
||||
$table = 'file_content';
|
||||
$filecontent = file_get_contents($file);
|
||||
$filehash = hash_file('sha256', $file);
|
||||
$size = filesize($file);
|
||||
$ifexists = DBFunctions($this->tablename, $this->DB)->CheckifUIDorHashKeyExist($filehash, $fieldstoselectarray = ['uid', 'hashkey'])['uid'] ?? false;
|
||||
if ($ifexists) {
|
||||
return $ifexists;
|
||||
}
|
||||
global $DB;
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
|
||||
$data = ['titlename' => $filename, 'hashkey' => $filehash, 'size_in_bytes' => $size];
|
||||
if ($toDB === true) {
|
||||
$data['content'] = $filecontent;
|
||||
} elseif (!$toDB) {
|
||||
$toDB = 'files/';
|
||||
|
||||
} else {
|
||||
if (!is_dir($toDB)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!endsWithSlash($toDB)) {
|
||||
$toDB .= "/";
|
||||
}
|
||||
if ($toDB !== true) {
|
||||
file_put_contents($toDB . $filehash, $filecontent);
|
||||
if (!file_exists($toDB . $filehash) or filesize($toDB . $filehash) !== $size) {
|
||||
return false;
|
||||
}
|
||||
$data['filelocation'] = $toDB;
|
||||
$data['content'] = '';
|
||||
}
|
||||
$newuid = insertintodb($DB, $table, $data);
|
||||
|
||||
return $newuid;
|
||||
}
|
||||
|
||||
function DeleteFileContent($uidorhashkey)
|
||||
{
|
||||
$where = [];
|
||||
if (!$uidorhashkey) {
|
||||
return false;
|
||||
}
|
||||
if (is_numeric($uidorhashkey)) {
|
||||
$where['uid'] = $uidorhashkey;
|
||||
} else {
|
||||
$where['hashkey'] = $uidorhashkey;
|
||||
}
|
||||
return deletefromdb('file_content', $where);
|
||||
}
|
||||
|
||||
function getFileContentDetails($uidorhashkey, $fieldstoselect = '')
|
||||
{
|
||||
return getDetailsbyUIDorHashkey('file_content', $uidorhashkey, $fieldstoselect);
|
||||
}
|
||||
|
||||
function getFileContentUIDbyHashkey($hashkey)
|
||||
{
|
||||
if (!$hashkey or is_numeric($hashkey)) {
|
||||
return false;
|
||||
}
|
||||
return $this->getFileContentDetails($hashkey, ['uid', 'hashkey'])['uid'] ?? false;
|
||||
}
|
||||
function getFileContentHashkeybyUID($UID)
|
||||
{
|
||||
if (!$UID or !is_numeric($UID)) {
|
||||
return false;
|
||||
}
|
||||
return $this->getFileContentDetails($UID, ['uid', 'hashkey'])['hashkey'] ?? false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function DB_FILE_CONTENT($DB = false)
|
||||
{
|
||||
return new DB_FILE_CONTENT($DB);
|
||||
}
|
||||
|
||||
|
||||
class FILE_CONTENT_QUICKMULTIPLESEARCH
|
||||
{
|
||||
|
||||
use DBClassSearch;
|
||||
public $data;
|
||||
|
||||
public $tablename = 'file_content';
|
||||
|
||||
private $parentidresults = [];
|
||||
public $DB;
|
||||
|
||||
public function __construct($data = [], $likefields = [], $fieldstoselectarray = '', $orderby = '', $noindex = 0, $whereappend = ' and ', $dateonlyarray = '', $newdata = false, $DB = false)
|
||||
{
|
||||
return $this->initialize($data, $likefields, $fieldstoselectarray, $orderby, $noindex, $whereappend, $dateonlyarray, $newdata, $DB);
|
||||
}
|
||||
|
||||
function FindName($uid)
|
||||
{
|
||||
return $this->Find($fieldname = 'uid', $contenttosearch = $uid, $exact = true)[0]['name'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function FILE_CONTENT_QUICKMULTIPLESEARCH($DB = false)
|
||||
{
|
||||
return new FILE_CONTENT_QUICKMULTIPLESEARCH($DB);
|
||||
}
|
||||
|
||||
|
||||
class DB_FILE_LIST
|
||||
{
|
||||
|
||||
public $DB = false;
|
||||
public $tablename = 'file_list';
|
||||
|
||||
use BASICDB;
|
||||
public function __construct($DB = false)
|
||||
{
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
if (!$DB) {
|
||||
return false;
|
||||
}
|
||||
if ($DB) {
|
||||
$this->DB = $DB;
|
||||
}
|
||||
if (!$this->tablename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function insertFileListintoDB($contentuid, $filename, $description = '', $tags = '', $categories = '', $added = '', $addedby = '', $hidden = 0)
|
||||
{
|
||||
|
||||
|
||||
if (!$contentuid or !$filename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!$addedby) {
|
||||
global $CurrentUserUID;
|
||||
$addedby = $CurrentUserUID;
|
||||
if (!$addedby) {
|
||||
$addedby = CurrentUserUID();
|
||||
}
|
||||
}
|
||||
if (!$added) {
|
||||
$added = serverdatetimesql();
|
||||
}
|
||||
$modified = serverdatetimesql();
|
||||
|
||||
if (!$description) {
|
||||
$description = '';
|
||||
}
|
||||
if (!$tags) {
|
||||
$tags = '';
|
||||
}
|
||||
if (!$categories) {
|
||||
$categories = '';
|
||||
}
|
||||
|
||||
$hash = generatenewhash($this->tablename);
|
||||
$categories = tryjsonencode($categories);
|
||||
|
||||
$datenow = serverdatetimesql();
|
||||
$tags = tryjsonencode($tags);
|
||||
|
||||
|
||||
$data = [
|
||||
'hashkey' => $hash,
|
||||
'contentuid' => $contentuid,
|
||||
'useruid_access_list' => tryjsonencode(''),
|
||||
'filename' => $filename,
|
||||
'description' => $description,
|
||||
'tags' => $tags,
|
||||
'categories' => $categories,
|
||||
'created' => $added,
|
||||
'modified' => $modified,
|
||||
'hidden' => $hidden,
|
||||
'addedby' => $addedby
|
||||
];
|
||||
|
||||
global $DB;
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
$key = insertintodb($DB, 'file_list', $data);
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
|
||||
function getFileListDetails($uidorhashkey, $fieldstoselect = '')
|
||||
{
|
||||
return getDetailsbyUIDorHashkey($this->tablename, $uidorhashkey, $fieldstoselect);
|
||||
}
|
||||
|
||||
|
||||
function getFileListUIDbyHashkey($hashkey)
|
||||
{
|
||||
if (!$hashkey or is_numeric($hashkey)) {
|
||||
return false;
|
||||
}
|
||||
return $this->getFileListDetails($hashkey, ['uid', 'hashkey'])['uid'] ?? false;
|
||||
}
|
||||
|
||||
function getFileListHashkeybyUID($UID)
|
||||
{
|
||||
if (!$UID or !is_numeric($UID)) {
|
||||
return false;
|
||||
}
|
||||
return $this->getFileListDetails($UID, ['uid', 'hashkey'])['hashkey'] ?? false;
|
||||
}
|
||||
|
||||
|
||||
function InsertFileListandFileContentFromFile($filelocation, $filename, $toDBtrueifFalseSateLocationtoSave = false, $description = '', $tags = '', $categories = '', $hidden = 0)
|
||||
{
|
||||
if (!$filelocation and !$filename) {
|
||||
return false;
|
||||
}
|
||||
$contentuid = DB_FILE_CONTENT()->NewFileContent($filelocation, $filename, $toDBtrueifFalseSateLocationtoSave);
|
||||
|
||||
|
||||
$filelistuid = $this->insertFileListintoDB($contentuid, $filename, $description, $tags, $categories, $added = '', $addedby = '', $hidden);
|
||||
|
||||
return $filelistuid;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
function DB_FILE_LIST($DB = false)
|
||||
{
|
||||
return new DB_FILE_LIST($DB);
|
||||
}
|
||||
|
||||
|
||||
class FILE_LIST_QUICKMULTIPLESEARCH
|
||||
{
|
||||
|
||||
use DBClassSearch;
|
||||
public $data;
|
||||
|
||||
public $tablename = 'file_list';
|
||||
|
||||
private $parentidresults = [];
|
||||
public $DB;
|
||||
|
||||
public function __construct($data = [], $likefields = [], $fieldstoselectarray = '', $orderby = '', $noindex = 0, $whereappend = ' and ', $dateonlyarray = '', $newdata = false, $DB = false)
|
||||
{
|
||||
return $this->initialize($data, $likefields, $fieldstoselectarray, $orderby, $noindex, $whereappend, $dateonlyarray, $newdata, $DB);
|
||||
}
|
||||
|
||||
function FindName($uid)
|
||||
{
|
||||
return $this->Find($fieldname = 'uid', $contenttosearch = $uid, $exact = true)[0]['name'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function FILE_LIST_QUICKMULTIPLESEARCH($DB = false)
|
||||
{
|
||||
return new FILE_CONTENT_QUICKMULTIPLESEARCH($DB);
|
||||
}
|
||||
368
app/Http/Controllers/Helpers/Legacy/Backup/DBEXT/MARKETDB.php
Normal file
368
app/Http/Controllers/Helpers/Legacy/Backup/DBEXT/MARKETDB.php
Normal file
@@ -0,0 +1,368 @@
|
||||
<?php
|
||||
$classMap['STORES'] = 'DB_STORES';
|
||||
$classMap['PRODUCTS'] = 'DB_PRODUCTS';
|
||||
$classMap['PRODUCTS_TRANSACTIONS'] = 'DB_PRODUCTS_TRANSACTIONS';
|
||||
$classMap['POS_TRANSACTIONS'] = 'DB_POS_TRANSACTIONS';
|
||||
$classMap['PRODUCTS_TRANSACTIONS_SESSIONS'] = 'DB_PRODUCTS_TRANSACTIONS_SESSIONS';
|
||||
$classMap['PRODUCTS_HISTORY'] = 'DB_PRODUCTSHISTORY';
|
||||
$classMap['CART'] = 'DB_CART';
|
||||
$classMap['SUPPLIERS'] = 'DB_SUPPLIERS';
|
||||
$classMap['SUPPLIERS'] = 'DB_SUPPLIERS';
|
||||
|
||||
class DB_CART extends DB_USERS
|
||||
{
|
||||
private function USERS()
|
||||
{
|
||||
return new DB_USERS();
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
}
|
||||
function getCartContents($uidorhashkey)
|
||||
{
|
||||
if (!$uidorhashkey) {
|
||||
return false;
|
||||
}
|
||||
$DB = $this->DB;
|
||||
$wherearray = [];
|
||||
if (is_numeric($uidorhashkey)) {
|
||||
$wherearray = ['uid' => $uidorhashkey];
|
||||
} else {
|
||||
$wherearray = ['hashkey' => $uidorhashkey];
|
||||
}
|
||||
|
||||
$DBQUERY = $this->USERS()->GetUserDatabyUID($uidorhashkey);
|
||||
if (!$DBQUERY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = tryjsondecode($DBQUERY['cart']) ?? [];
|
||||
return $result;
|
||||
}
|
||||
function getCartContentsAndComputations($uidorhashkey){
|
||||
$cartContents= $this->getCartContents($uidorhashkey);
|
||||
if (!$cartContents){return false;}
|
||||
return $this->getCartDetailsComputations($cartContents);
|
||||
}
|
||||
function getCartDetailsComputations($cartdata)
|
||||
{
|
||||
if (!$cartdata || !is_array($cartdata)) {
|
||||
return false;
|
||||
}
|
||||
$productsClass = new DB_PRODUCTS_QUICK_MULTIPLESEARCH;
|
||||
$storeClass = new DB_STORES_QUICK_MULTIPLESEARCH;
|
||||
$CartDetails = [];
|
||||
$cartproducts = [];
|
||||
|
||||
|
||||
|
||||
|
||||
foreach ($cartdata as $value) {
|
||||
|
||||
$cart_product_hashkey = $value['hashkey'] ?? $value[0];
|
||||
$cart_product_quantity = $value['quantity'] ?? $value[1];
|
||||
$cart_product_added = $value['added'] ?? $value[2];
|
||||
$cart_product_modified = $value['modified'] ?? $value[3];
|
||||
|
||||
$productdata = $productsClass->getDetailsbyUIDorHashkey($cart_product_hashkey) ?? false;
|
||||
if (!$productdata) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
$product_active = $productdata['status'] === 'active';
|
||||
$product_available = $productdata['available'] >= $cart_product_quantity;
|
||||
|
||||
$productstore_uid = $productdata['storeuid'];
|
||||
|
||||
$productstore_hash = $storeClass->getHASHfromUID($productstore_uid);
|
||||
|
||||
$productphoto_array = tryjsondecode($productdata['photourl']);
|
||||
if (!is_array($productphoto_array)) {
|
||||
$productphotoarray = [];
|
||||
}
|
||||
$productphotourl = $productphoto_array[0] ?? null;
|
||||
|
||||
$productprice = $productdata['price'];
|
||||
$product_unit = $productdata['unitname'];
|
||||
$product_name = $productdata['name'];
|
||||
if ($cart_product_quantity < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$cartproducts[] = [
|
||||
'hashkey' => $cart_product_hashkey,
|
||||
'quantity' => $cart_product_quantity,
|
||||
'price' => $productprice,
|
||||
'unit' => $product_unit,
|
||||
'name' => $product_name,
|
||||
'photourl' => $productphotourl,
|
||||
'added' => $cart_product_added,
|
||||
'modified' => $cart_product_modified,
|
||||
'store' => $productstore_hash,
|
||||
'active' => $product_active,
|
||||
'available' => $product_available,
|
||||
'subtotal' => $productprice * $cart_product_quantity
|
||||
];
|
||||
}
|
||||
|
||||
$total = array_sum(array_column($cartproducts, 'subtotal'));
|
||||
if ($total < 2) {
|
||||
return false;
|
||||
}
|
||||
$CartDetails['details'] = [
|
||||
'total' => $total,
|
||||
'shipping' => 'Unknown'
|
||||
];
|
||||
$CartDetails['cart'] = $cartproducts;
|
||||
|
||||
return $CartDetails;
|
||||
}
|
||||
function SetCartContents($uidorhashkey, $newcontentsArray)
|
||||
{
|
||||
if (!$uidorhashkey) {
|
||||
return false;
|
||||
}
|
||||
$DB = $this->DB;
|
||||
$newcontentsArray = tryjsonencode($newcontentsArray);
|
||||
$newcontentsArray = ['cart' => $newcontentsArray];
|
||||
$DBNow = DBQUERY()->USERS()->ModifyUser($newcontentsArray, $uidorhashkey);
|
||||
return $DBNow;
|
||||
}
|
||||
function AddCartContentsSingleProduct($uidorhashkey, $producthashkey, $additionalquantity)
|
||||
{
|
||||
if (!$uidorhashkey) {
|
||||
return false;
|
||||
}
|
||||
$exists = DBQUERY()->USERS()->GetUserDatabyUID($uidorhashkey);
|
||||
if (!$exists) {
|
||||
return false;
|
||||
}
|
||||
if (!$producthashkey || is_numeric($producthashkey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$additionalquantity || !is_numeric($additionalquantity)) {
|
||||
return false;
|
||||
}
|
||||
$contents = $this->getCartContents($uidorhashkey);
|
||||
if ($contents) {
|
||||
$producthashkeys = array_column($contents, 0);
|
||||
} else {
|
||||
$contents = [];
|
||||
$producthashkeys = [];
|
||||
}
|
||||
|
||||
$targetkey = array_search($producthashkey, $producthashkeys);
|
||||
if ($targetkey === false) {
|
||||
$contents[] = [$producthashkey, $additionalquantity, serverdatetimesql(), serverdatetimesql()];
|
||||
} else {
|
||||
$content_details = $contents[$targetkey];
|
||||
$currentquantity = $content_details[1];
|
||||
$currentdate_added = $content_details[2];
|
||||
$newdate_modfied = serverdatetimesql();
|
||||
$newquantity = $currentquantity + $additionalquantity;
|
||||
if ($newquantity < 0) {
|
||||
return false;
|
||||
}
|
||||
$contents[$targetkey] = [$producthashkey, $newquantity, $currentdate_added, $newdate_modfied];
|
||||
}
|
||||
|
||||
return $this->SetCartContents($uidorhashkey, $contents);
|
||||
}
|
||||
|
||||
function EmptyCartContents($uidorhashkey)
|
||||
{
|
||||
if (!$uidorhashkey) {
|
||||
return false;
|
||||
}
|
||||
return $this->SetCartContents($uidorhashkey, []);
|
||||
}
|
||||
function DeleteCartContents($uidorhashkey, $producthashkey)
|
||||
{
|
||||
if (!$uidorhashkey) {
|
||||
return false;
|
||||
}
|
||||
if (!$producthashkey || is_numeric($producthashkey)) {
|
||||
return false;
|
||||
}
|
||||
$contents = $this->getCartContents($uidorhashkey);
|
||||
$producthashkeys = array_column($contents, 0);
|
||||
$targetkey = array_search($producthashkey, $producthashkeys);
|
||||
if ($targetkey === false) {
|
||||
unset($contents[$targetkey]);
|
||||
$contents = array_values($contents);
|
||||
}
|
||||
return $this->SetCartContents($uidorhashkey, $contents);
|
||||
}
|
||||
|
||||
function CheckifProductisAllowedtoAddtoCart($producthashkeyoruid, $quantity)
|
||||
{
|
||||
//returns the product details if allowed, false if not
|
||||
$ProductDetails = DB_PRODUCTS()->getDetailsbyUIDorHashkey($producthashkeyoruid);
|
||||
if (!$ProductDetails || $ProductDetails['status'] !== 'active' || $ProductDetails['available'] < 1) {
|
||||
return false;
|
||||
}
|
||||
if ($quantity > $ProductDetails['available']) {
|
||||
return false;
|
||||
}
|
||||
return $ProductDetails;
|
||||
|
||||
}
|
||||
function SetProductQuantityCartContent($uidorhashkey, $producthashkeyoruid, $quantity)
|
||||
{
|
||||
if (!$uidorhashkey || !$producthashkeyoruid || $quantity === null) {
|
||||
return false;
|
||||
}
|
||||
$producthashkeyifAllowed = $this->CheckifProductisAllowedtoAddtoCart($producthashkeyoruid, $quantity)['hashkey'] ?? false;
|
||||
if (!$producthashkeyifAllowed) {
|
||||
return false;
|
||||
}
|
||||
$producthashkeyoruid = $producthashkeyifAllowed;
|
||||
$contents = $this->getCartContents($uidorhashkey);
|
||||
if (!$contents) {
|
||||
$contents = [];
|
||||
}
|
||||
|
||||
$producthashcolumns = array_column($contents, 0);
|
||||
|
||||
$targetkey = array_search($producthashkeyoruid, $producthashcolumns);
|
||||
|
||||
if ($targetkey === false) {
|
||||
$contents[] = [$producthashkeyoruid, $quantity, serverdatetimesql(), serverdatetimesql()];
|
||||
} else {
|
||||
$content_details = $contents[$targetkey];
|
||||
$currentquantity = $content_details[1];
|
||||
$currentdate_added = $content_details[2];
|
||||
$newdate_modfied = serverdatetimesql();
|
||||
$contents[$targetkey] = [$producthashkeyoruid, $quantity, $currentdate_added, $newdate_modfied];
|
||||
}
|
||||
|
||||
|
||||
$updatecontent = $this->SetCartContents($uidorhashkey, $contents);
|
||||
return $updatecontent;
|
||||
|
||||
}
|
||||
function ShowCartStructure()
|
||||
{
|
||||
$structure = ['contenthashkey', 'quantity', 'date_added', 'date_modified'];
|
||||
e($structure);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function CartDB($DB = false)
|
||||
{
|
||||
return new DB_CART($DB);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
class MARKET_MAIN
|
||||
{
|
||||
public $DB = false;
|
||||
|
||||
public function __construct($DB = false)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
function isNewTransactionAllowed($createdby_details, $createdfor_details, $ownerdetails)
|
||||
{
|
||||
|
||||
if (!$createdby_details || !$createdfor_details || !$ownerdetails) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$parentflagAllowed = false;
|
||||
$acct_typeflagAllowed = false;
|
||||
|
||||
$ownerUID = $ownerdetails['uid'] ?? false;
|
||||
if (!$ownerUID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_array($createdby_details)) {
|
||||
$createdby_type = $createdby_details;
|
||||
} else {
|
||||
$createdby_type = $createdby_details['acct_type'];
|
||||
}
|
||||
|
||||
if (!is_array($createdby_details)) {
|
||||
$createdfor_type = $createdfor_details;
|
||||
} else {
|
||||
$createdfor_type = $createdfor_details['acct_type'];
|
||||
}
|
||||
|
||||
$createdby_Allowed_Types = $createdby_type === 'storeowner' || $createdby_type === 'storemanager' || $createdby_type === 'ult' || $createdby_type === 'superoperator';
|
||||
$createdfor_Allowed_Types = $createdby_type === 'storeowner' || $createdby_type === 'storemanager';
|
||||
$iscreatedbyULT = $createdby_type === 'ult';
|
||||
|
||||
$createdfor_Parent = $createdfor_details['parentuid'];
|
||||
$createdby_Parent = $createdby_details['parentuid'];
|
||||
|
||||
|
||||
|
||||
if ($createdfor_Parent === $ownerUID) {
|
||||
$parentflagAllowed = true;
|
||||
}
|
||||
|
||||
|
||||
if ($createdby_Allowed_Types && $createdfor_Allowed_Types) {
|
||||
$acct_typeflagAllowed = true;
|
||||
}
|
||||
|
||||
|
||||
if ($iscreatedbyULT) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if ($acct_typeflagAllowed && $parentflagAllowed) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function getCityFromAddress($address)
|
||||
{
|
||||
$citiesfile = file_get_contents('lib/cities.json');
|
||||
$cities = json_decode($citiesfile) ?? false;
|
||||
$cities = array_column($cities, 'name') ?? false;
|
||||
|
||||
if (!$cities) {
|
||||
return false;
|
||||
}
|
||||
foreach ($cities as $city) {
|
||||
if (stripos($address, $city) !== false) {
|
||||
return $city;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function MARKET_MAIN()
|
||||
{
|
||||
return new MARKET_MAIN();
|
||||
}
|
||||
|
||||
|
||||
require_once('MarketDB/products.db.php');
|
||||
require_once('MarketDB/productshistory.db.php');
|
||||
require_once('MarketDB/product_transactions.db.php');
|
||||
require_once('MarketDB/product_transactions_sessions.db.php');
|
||||
require_once('MarketDB/product_transactions_sessions.db.php');
|
||||
require_once('MarketDB/pos_transactions.db.php');
|
||||
require_once('MarketDB/pos_transactions_sessions.db.php');
|
||||
require_once('MarketDB/stores.db.php');
|
||||
require_once('MarketDB/suppliers.db.php');
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
|
||||
|
||||
class DB_POS_TRANSACTIONS
|
||||
{
|
||||
public $DB = false;
|
||||
public $tablename = 'pos_transactions';
|
||||
|
||||
use BASICDB;
|
||||
public function __construct($DB = false)
|
||||
{
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
if (!$DB) {
|
||||
return false;
|
||||
}
|
||||
if ($DB) {
|
||||
$this->DB = $DB;
|
||||
}
|
||||
if (!$this->tablename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
function NewPOSTransaction()
|
||||
{
|
||||
|
||||
$data = [
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
];
|
||||
|
||||
|
||||
return $this->InsertBasicDBHashCreatedModified($data);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class DB_POS_TRANSACTIONS_QUICK_MULTIPLESEARCH
|
||||
{
|
||||
use DBClassSearch;
|
||||
public $data;
|
||||
|
||||
public $tablename = 'pos_transactions';
|
||||
|
||||
private $parentidresults = [];
|
||||
|
||||
|
||||
public function __construct($data = [], $likefields = [], $fieldstoselectarray = '', $orderby = '', $noindex = 0, $whereappend = ' and ', $dateonlyarray = '', $newdata = false)
|
||||
{
|
||||
return $this->initialize($data, $likefields, $fieldstoselectarray, $orderby, $noindex, $whereappend, $dateonlyarray, $newdata);
|
||||
}
|
||||
|
||||
function FindName($uid)
|
||||
{
|
||||
return $this->Find($fieldname = 'uid', $contenttosearch = $uid, $exact = true)[0]['name'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function DB_POS_TRANSACTIONS($DB = false)
|
||||
{
|
||||
return new DB_POS_TRANSACTIONS($DB);
|
||||
}
|
||||
|
||||
function DB_POS_TRANSACTIONS_QUICK_MULTIPLESEARCH($DB = false)
|
||||
{
|
||||
return new DB_POS_TRANSACTIONS_QUICK_MULTIPLESEARCH($DB);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
|
||||
class DB_POS_TRANSACTIONS_SESSIONS
|
||||
{
|
||||
public $DB = false;
|
||||
public $tablename = 'pos_transactions_sessions';
|
||||
|
||||
use BASICDB;
|
||||
public function __construct($DB = false)
|
||||
{
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
if (!$DB) {
|
||||
return false;
|
||||
}
|
||||
if ($DB) {
|
||||
$this->DB = $DB;
|
||||
}
|
||||
if (!$this->tablename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
function NewRecord()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class DB_POS_TRANSACTIONS_SESSIONS_QUICK_MULTIPLESEARCH
|
||||
{
|
||||
use DBClassSearch;
|
||||
public $data;
|
||||
|
||||
public $tablename = 'pos_transactions_sessions';
|
||||
|
||||
private $parentidresults = [];
|
||||
|
||||
|
||||
public function __construct($data = [], $likefields = [], $fieldstoselectarray = '', $orderby = '', $noindex = 0, $whereappend = ' and ', $dateonlyarray = '', $newdata = false)
|
||||
{
|
||||
return $this->initialize($data, $likefields, $fieldstoselectarray, $orderby, $noindex, $whereappend, $dateonlyarray, $newdata);
|
||||
}
|
||||
|
||||
function FindName($uid)
|
||||
{
|
||||
return $this->Find($fieldname = 'uid', $contenttosearch = $uid, $exact = true)[0]['name'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function DB_POS_TRANSACTIONS_SESSIONS($DB = false)
|
||||
{
|
||||
return new DB_POS_TRANSACTIONS_SESSIONS($DB);
|
||||
}
|
||||
function DB_POS_TRANSACTIONS_SESSIONS_QUICK_MULTIPLESEARCH($DB = false)
|
||||
{
|
||||
return new DB_POS_TRANSACTIONS_SESSIONS_QUICK_MULTIPLESEARCH($DB);
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
|
||||
class DB_PRODUCTS_TRANSACTIONS
|
||||
{
|
||||
public $DB = false;
|
||||
public $tablename = 'products_transactions';
|
||||
|
||||
use BASICDB;
|
||||
public function __construct($DB = false)
|
||||
{
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
if (!$DB) {
|
||||
return false;
|
||||
}
|
||||
if ($DB) {
|
||||
$this->DB = $DB;
|
||||
}
|
||||
if (!$this->tablename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
function NewProductTransactions($productuid, $storeuid, $quantity, $transactiontype, $transactionsessionhash = '', $subtype = '', $createdfor = '', $price = '', $owneruid = '', $createdby = '', $name = '', $description = '', $transactiondata = '', $remarks = '')
|
||||
{
|
||||
|
||||
if (!$productuid || !$storeuid || !$transactiontype || !$quantity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
global $CurrentUserUID;
|
||||
$createdby = $CurrentUserUID;
|
||||
if (!$createdby) {
|
||||
$createdby = CurrentUserUID();
|
||||
}
|
||||
|
||||
if (!$createdfor) {
|
||||
$createdfor = $createdby;
|
||||
}
|
||||
|
||||
$data = [];
|
||||
|
||||
$AddtoDataifNOTEmpty = function ($varname, $varvalue) use (&$data) {
|
||||
if (!$varname || !$varvalue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_array($varvalue)) {
|
||||
$varvalue = tryjsonencode($varvalue);
|
||||
}
|
||||
$data[$varname] = $varvalue;
|
||||
};
|
||||
|
||||
$date = serverdatetimesql();
|
||||
$created = $date;
|
||||
$modified = $date;
|
||||
|
||||
if (!$transactionsessionhash) {
|
||||
$transactionsessionhash = DBQUERY()->PRODUCTS_TRANSACTIONS_SESSIONS()->NewProductsTransactionSession($storeuid);
|
||||
} else {
|
||||
if (!DBQUERY()->PRODUCTS_TRANSACTIONS_SESSIONS()->checkifUIDorHashKeyexist($transactionsessionhash)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (!$transactiondata) {
|
||||
$transactiondata = [];
|
||||
}
|
||||
|
||||
$storeexists = DBQUERY()->STORES()->CheckifUIDorHashKeyExist($storeuid);
|
||||
if (!$storeexists) {
|
||||
return false;
|
||||
}
|
||||
$productexists = DBQUERY()->PRODUCTS()->CheckifUIDorHashKeyExist($productuid);
|
||||
if (!$productexists) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$logs = [$date, 'Created New Product Transaction by useruid(' . $createdby . ') at ' . $modified . ' .'];
|
||||
|
||||
$AddtoDataifNOTEmpty('productuid', $productuid);
|
||||
$AddtoDataifNOTEmpty('storeuid', $storeuid);
|
||||
$AddtoDataifNOTEmpty('quantity', $quantity);
|
||||
$AddtoDataifNOTEmpty('transactiontype', $transactiontype);
|
||||
$AddtoDataifNOTEmpty('transactionsessionhash', $transactionsessionhash);
|
||||
$AddtoDataifNOTEmpty('subtype', $subtype);
|
||||
$AddtoDataifNOTEmpty('createdfor', $createdfor);
|
||||
$AddtoDataifNOTEmpty('price', $price);
|
||||
$AddtoDataifNOTEmpty('owneruid', $owneruid);
|
||||
$AddtoDataifNOTEmpty('createdby', $createdby);
|
||||
$AddtoDataifNOTEmpty('name', $name);
|
||||
$AddtoDataifNOTEmpty('description', $description);
|
||||
$AddtoDataifNOTEmpty('description', $remarks);
|
||||
$AddtoDataifNOTEmpty('transactiondata', $transactiondata);
|
||||
$AddtoDataifNOTEmpty('transactiondata', $logs);
|
||||
$AddtoDataifNOTEmpty('created', $created);
|
||||
$AddtoDataifNOTEmpty('modified', $modified);
|
||||
|
||||
return $this->InsertIntoDB($data);
|
||||
|
||||
}
|
||||
|
||||
|
||||
function getTransactionsSessions()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DB_PRODUCTS_TRANSACTIONS_QUICK_MULTIPLESEARCH
|
||||
{
|
||||
use DBClassSearch;
|
||||
public $data;
|
||||
|
||||
public $tablename = 'products_transactions';
|
||||
|
||||
private $parentidresults = [];
|
||||
|
||||
|
||||
public function __construct($data = [], $likefields = [], $fieldstoselectarray = '', $orderby = '', $noindex = 0, $whereappend = ' and ', $dateonlyarray = '', $newdata = false)
|
||||
{
|
||||
return $this->initialize($data, $likefields, $fieldstoselectarray, $orderby, $noindex, $whereappend, $dateonlyarray, $newdata);
|
||||
}
|
||||
|
||||
function FindName($uid)
|
||||
{
|
||||
return $this->Find($fieldname = 'uid', $contenttosearch = $uid, $exact = true)[0]['name'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function DB_PRODUCTS_TRANSACTIONS($DB = false)
|
||||
{
|
||||
return new DB_PRODUCTS_TRANSACTIONS($DB);
|
||||
}
|
||||
|
||||
function DB_PRODUCTS_TRANSACTIONS_QUICK_MULTIPLESEARCH($DB = false)
|
||||
{
|
||||
return new DB_PRODUCTS_TRANSACTIONS_QUICK_MULTIPLESEARCH($DB);
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
|
||||
class DB_PRODUCTS_TRANSACTIONS_SESSIONS
|
||||
{
|
||||
public $DB = false;
|
||||
public $tablename = 'products_transactions_sessions';
|
||||
|
||||
use BASICDB;
|
||||
public function __construct($DB = false)
|
||||
{
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
if (!$DB) {
|
||||
return false;
|
||||
}
|
||||
if ($DB) {
|
||||
$this->DB = $DB;
|
||||
}
|
||||
if (!$this->tablename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function NewProductsTransactionSession($storeuid, $status = 'open', $name = '', $description = '', $category = '', $subtype = '', $createdby = '', $createdfor = '', $remarks = '')
|
||||
{
|
||||
if (!$storeuid) {
|
||||
return false;
|
||||
}
|
||||
$store = DB_STORES()->getDetailsbyUIDorHashkey($storeuid);
|
||||
|
||||
if (!$storeuid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$store_owneruid = $store['owneruid'];
|
||||
$store_manageruid = $store['manageruid'];
|
||||
|
||||
$date = serverdatetimesql();
|
||||
$created = $date;
|
||||
$modified = $date;
|
||||
|
||||
global $CurrentUserUID;
|
||||
|
||||
if (!$createdby) {
|
||||
$createdby = $CurrentUserUID;
|
||||
}
|
||||
if (!$createdby) {
|
||||
$createdby = CurrentUserUID();
|
||||
}
|
||||
|
||||
if (!$createdfor) {
|
||||
$createdfor = $createdby;
|
||||
}
|
||||
|
||||
$createdby_details = DBQUERY()->USERS()->getDetailsbyUIDorHashkey($createdby);
|
||||
if (!$createdby_details) {
|
||||
return false;
|
||||
}
|
||||
if ($createdby !== $createdfor) {
|
||||
$createdfor_details = DBQUERY()->USERS()->getDetailsbyUIDorHashkey($createdfor);
|
||||
} else {
|
||||
$createdfor_details = $createdby_details;
|
||||
}
|
||||
|
||||
$ownerdetails = DBQUERY()->USERS()->getDetailsbyUIDorHashkey($store_owneruid);
|
||||
if (!$ownerdetails) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!MARKET_MAIN()->isNewTransactionAllowed($createdby_details, $createdfor_details, $ownerdetails)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$logs = [$date, 'Created New Transaction by uid(' . $createdby . ')'];
|
||||
|
||||
$hashkey = $this->GenerateNewHash();
|
||||
$data = [
|
||||
'hashkey' => $hashkey,
|
||||
'created' => $date,
|
||||
'modified' => $date,
|
||||
'name' => $name,
|
||||
'description' => $description,
|
||||
'category' => $category,
|
||||
'subtype' => $subtype,
|
||||
'createdby' => $createdby,
|
||||
'createdfor' => $createdfor,
|
||||
'remarks' => $remarks,
|
||||
'logs' => $logs,
|
||||
'status' => $status
|
||||
];
|
||||
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if (!$value) {
|
||||
unset($data[$key]);
|
||||
}
|
||||
if (is_array($value)) {
|
||||
$data[$key] = tryjsonencode($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $insert = $this->InsertIntoDB($data);
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class DB_PRODUCTS_TRANSACTIONS_SESSIONS_QUICK_MULTIPLESEARCH
|
||||
{
|
||||
use DBClassSearch;
|
||||
public $data;
|
||||
|
||||
public $tablename = 'products_transactions_sessions';
|
||||
|
||||
private $parentidresults = [];
|
||||
|
||||
|
||||
public function __construct($data = [], $likefields = [], $fieldstoselectarray = '', $orderby = '', $noindex = 0, $whereappend = ' and ', $dateonlyarray = '', $newdata = false)
|
||||
{
|
||||
return $this->initialize($data, $likefields, $fieldstoselectarray, $orderby, $noindex, $whereappend, $dateonlyarray, $newdata);
|
||||
}
|
||||
|
||||
function FindName($uid)
|
||||
{
|
||||
return $this->Find($fieldname = 'uid', $contenttosearch = $uid, $exact = true)[0]['name'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function DB_PRODUCTS_TRANSACTIONS_SESSIONS($DB = false)
|
||||
{
|
||||
return new DB_PRODUCTS_TRANSACTIONS_SESSIONS($DB);
|
||||
}
|
||||
function DB_PRODUCTS_TRANSACTIONS_SESSIONS_QUICK_MULTIPLESEARCH($DB = false)
|
||||
{
|
||||
return new DB_PRODUCTS_TRANSACTIONS_SESSIONS_QUICK_MULTIPLESEARCH($DB);
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
<?php
|
||||
|
||||
|
||||
class DB_PRODUCTS
|
||||
{
|
||||
|
||||
public $DB = false;
|
||||
public $tablename = 'products';
|
||||
|
||||
use BASICDB;
|
||||
public function __construct($DB = false)
|
||||
{
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
if (!$DB) {
|
||||
return false;
|
||||
}
|
||||
if ($DB) {
|
||||
$this->DB = $DB;
|
||||
}
|
||||
if (!$this->tablename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function NewWholesaleProduct(
|
||||
$name,
|
||||
$description,
|
||||
$price,
|
||||
$unitname,
|
||||
$storeuid,
|
||||
$available = 0,
|
||||
$photourl = '',
|
||||
$barcode = '',
|
||||
$specs = '',
|
||||
$owneruid = '',
|
||||
$sold = '',
|
||||
$category = '',
|
||||
$subcategory = '',
|
||||
$remarks = '',
|
||||
$status = '',
|
||||
$sku = '',
|
||||
$shortcode = '',
|
||||
$shortname = '',
|
||||
$qrcode = '',
|
||||
$reviews = '',
|
||||
$createdby = '',
|
||||
$rating = '',
|
||||
$createdfor = ''
|
||||
) {
|
||||
if (!$specs) {
|
||||
$specs = [];
|
||||
}
|
||||
$specs[]='wholesale';
|
||||
|
||||
return $this->NewProduct($name,
|
||||
$description,
|
||||
$price,
|
||||
$unitname,
|
||||
$storeuid,
|
||||
$available,
|
||||
$photourl,
|
||||
$barcode,
|
||||
$specs,
|
||||
$owneruid ,
|
||||
$sold,
|
||||
$category,
|
||||
$subcategory,
|
||||
$remarks,
|
||||
$status,
|
||||
$sku,
|
||||
$shortcode,
|
||||
$shortname,
|
||||
$qrcode,
|
||||
$reviews,
|
||||
$createdby,
|
||||
$rating,
|
||||
$createdfor);
|
||||
|
||||
}
|
||||
|
||||
function NewProduct(
|
||||
$name,
|
||||
$description,
|
||||
$price,
|
||||
$unitname,
|
||||
$storeuid,
|
||||
$available = 0,
|
||||
$photourl = '',
|
||||
$barcode = '',
|
||||
$specs = '',
|
||||
$owneruid = '',
|
||||
$sold = '',
|
||||
$category = '',
|
||||
$subcategory = '',
|
||||
$remarks = '',
|
||||
$status = '',
|
||||
$sku = '',
|
||||
$shortcode = '',
|
||||
$shortname = '',
|
||||
$qrcode = '',
|
||||
$reviews = '',
|
||||
$createdby = '',
|
||||
$rating = '',
|
||||
$createdfor = ''
|
||||
) {
|
||||
|
||||
if (!$name || !$description || !$price || !$unitname || !$storeuid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$storedata = DBQUERY()->STORES()->CheckifUIDorHashKeyExist($storeuid);
|
||||
|
||||
if (!$storedata) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$data = [];
|
||||
$addtofinalDBVar = function ($varvalue, $varname, $tryjson = false) use (&$data) {
|
||||
if ($varvalue) {
|
||||
if ($tryjson and is_array($varvalue)) {
|
||||
$varvalue = tryjsonencode($varvalue);
|
||||
}
|
||||
$data[$varname] = $varvalue;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
global $CurrentUserUID;
|
||||
|
||||
if (!$createdby) {
|
||||
$createdby = $CurrentUserUID;
|
||||
}
|
||||
|
||||
if (!$createdby) {
|
||||
$createdby = CurrentUserUID();
|
||||
}
|
||||
|
||||
if (!$createdfor) {
|
||||
$createdfor = $createdby;
|
||||
}
|
||||
|
||||
if (!$status) {
|
||||
$status = 'active';
|
||||
} else {
|
||||
$status = strtolower($status);
|
||||
}
|
||||
|
||||
|
||||
$ownerUIDCheckifParent = function ($owneruid, $createdfor, $createdby) {
|
||||
|
||||
$createdby_isParentofOwner = DB_USERS()->isTargetUserAChildofParent($owneruid, $createdby);
|
||||
if ($createdby_isParentofOwner) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$createdfor_isParentofOwner = DB_USERS()->isTargetUserAChildofParent($owneruid, $createdfor);
|
||||
if ($createdfor_isParentofOwner) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$Owner_isParentofCreatedby = DB_USERS()->isTargetUserAChildofParent($createdby, $owneruid);
|
||||
if ($Owner_isParentofCreatedby) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
$createdby_details = DB_USERS()->getDetailsbyUIDorHashkey($createdby);
|
||||
if (!$createdby_details) {
|
||||
return false;
|
||||
}
|
||||
if ($createdby !== $createdfor) {
|
||||
$createdfor_details = DB_USERS()->getDetailsbyUIDorHashkey($createdfor);
|
||||
} else {
|
||||
$createdfor_details = $createdby_details;
|
||||
}
|
||||
|
||||
if (!$createdfor_details) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$createdby_type = $createdby_details['acct_type'];
|
||||
$createdby_uid = $createdby_details['uid'];
|
||||
$createdbyfor_type = $createdfor_details['acct_type'];
|
||||
$createdfor_uid = $createdfor_details['uid'];
|
||||
|
||||
$storeuid_details = $storedata;
|
||||
if (!$storeuid) {
|
||||
return false;
|
||||
}
|
||||
$storeuid = $storeuid_details['uid'];
|
||||
|
||||
$storeuid_owneruid = $storeuid_details['owneruid'];
|
||||
|
||||
|
||||
if (!$owneruid) {
|
||||
$owneruid = $storeuid_owneruid;
|
||||
}
|
||||
|
||||
$createdby_is_StoreOwner = $createdby_type === 'storeowner';
|
||||
$createdby_is_StoreManager = $createdby_type === 'storemanager';
|
||||
$createdfor_is_StoreOwner = $createdbyfor_type === 'storeowner';
|
||||
$createdfor_is_StoreManger = $createdbyfor_type === 'storemanager';
|
||||
$createdby_is_Ult = $createdby_type === 'ult';
|
||||
$createdby_is_Operator = $createdby_type === 'operator';
|
||||
$createdby_is_Admin = $createdby_type === 'admincore';
|
||||
|
||||
|
||||
|
||||
|
||||
if (!$createdby_is_StoreManager && !$createdby_is_StoreOwner && !$createdfor_is_StoreOwner && !$createdfor_is_StoreManger && !$createdby_is_Ult && !$createdby_is_Operator && !$createdby_is_Admin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if ($owneruid) {
|
||||
$owner_details = DBQUERY()->USERS()->getDetailsbyUIDorHashkey($owneruid);
|
||||
if (!$owner_details) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$ownerUIDCheckifParent($owneruid, $createdfor, $createdby)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$createdby_is_SameUIDAsStoreOwner = $createdby_uid === $storeuid_owneruid;
|
||||
$createdfor_is_SameUIDAsStoreOwner = $createdfor_uid === $storeuid_owneruid;
|
||||
|
||||
$isOwnerofStore = $createdby_is_SameUIDAsStoreOwner || $createdfor_is_SameUIDAsStoreOwner || $createdby_is_Ult;
|
||||
|
||||
|
||||
if (!$isOwnerofStore) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$userdata = DB_USERS()->GetUserDatabyUID($createdby, ['username', 'nickname']);
|
||||
if (!$userdata) {
|
||||
return false;
|
||||
}
|
||||
$username = $userdata['username'] ?? 'unknown';
|
||||
$created = serverdatetimesql();
|
||||
$logs = [serverdatetimesql(), 'Product Created On ' . serverdatetimesql() . ' by useruid(' . $createdby . ')'];
|
||||
$hash = generatenewhash($this->tablename);
|
||||
|
||||
$addtofinalDBVar($name, 'name');
|
||||
$addtofinalDBVar($description, 'description');
|
||||
$addtofinalDBVar($price, 'price');
|
||||
$addtofinalDBVar($unitname, 'unitname');
|
||||
$addtofinalDBVar($storeuid, 'storeuid');
|
||||
$addtofinalDBVar($available, 'available');
|
||||
$addtofinalDBVar($photourl, 'photourl', true);
|
||||
$addtofinalDBVar($barcode, 'barcode');
|
||||
$addtofinalDBVar($specs, 'specs', 1);
|
||||
$addtofinalDBVar($owneruid, 'owneruid');
|
||||
$addtofinalDBVar($sold, 'sold');
|
||||
$addtofinalDBVar($category, 'category');
|
||||
$addtofinalDBVar($subcategory, 'subcategory');
|
||||
$addtofinalDBVar($remarks, 'remarks');
|
||||
$addtofinalDBVar($status, 'status');
|
||||
$addtofinalDBVar($sku, 'sku');
|
||||
$addtofinalDBVar($shortcode, 'shortcode');
|
||||
$addtofinalDBVar($shortname, 'shortname');
|
||||
$addtofinalDBVar($qrcode, 'qrcode');
|
||||
$addtofinalDBVar($rating, 'rating');
|
||||
$addtofinalDBVar($reviews, 'reviews', 1);
|
||||
$addtofinalDBVar($createdby, 'createdby');
|
||||
$addtofinalDBVar($createdfor, 'createdfor');
|
||||
$addtofinalDBVar($logs, 'logs');
|
||||
$addtofinalDBVar($hash, 'hashkey');
|
||||
$addtofinalDBVar($created, 'created');
|
||||
|
||||
|
||||
global $DB;
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
$key = insertintodb($DB, 'products', $data);
|
||||
|
||||
if ($key) {
|
||||
DBQUERY()->PRODUCTS_HISTORY()->NewProductHistory($data);
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
function ShowProductsbyStoreUID($uidorhashkey, $onlyactive = true, $fieldstoselect = [])
|
||||
{
|
||||
if (!$uidorhashkey) {
|
||||
return false;
|
||||
}
|
||||
if (!is_numeric($uidorhashkey)) {
|
||||
$uidorhashkey = DB_STORES()->getUIDfromHashkey($uidorhashkey) ?? false;
|
||||
if (!$uidorhashkey) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$data = ['storeuid' => $uidorhashkey];
|
||||
|
||||
if ($onlyactive) {
|
||||
$data['status'] = 'active';
|
||||
}
|
||||
|
||||
return $data = $this->ListFromDB($data, [], $fieldstoselect);
|
||||
}
|
||||
|
||||
function getReviewsbyUID($uidorhashkey)
|
||||
{
|
||||
if (!$uidorhashkey) {
|
||||
return false;
|
||||
}
|
||||
$productdetails = $this->getDetailsbyUIDorHashkey($uidorhashkey);
|
||||
if (!$productdetails) {
|
||||
return false;
|
||||
}
|
||||
return $reviews = tryjsondecode($productdetails['reviews']) ?? '';
|
||||
}
|
||||
|
||||
function setReviewsbyUID($uidorhashkey, $reviews)
|
||||
{
|
||||
if (!$uidorhashkey) {
|
||||
return false;
|
||||
}
|
||||
if ($reviews) {
|
||||
$reviews = tryjsonencode($reviews);
|
||||
}
|
||||
return $this->ModifyDBSinglefieldbyUID($uidorhashkey, 'reviews', $reviews);
|
||||
}
|
||||
function addReviewbyUID($uidorhashkey, $useruid, $rating, $content)
|
||||
{
|
||||
if (!$uidorhashkey || !$useruid || !$rating || !$content) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$reviews = $this->getReviewsbyUID($uidorhashkey);
|
||||
if ($reviews === false) {
|
||||
return false;
|
||||
}
|
||||
$lastelement = $reviews[count($reviews) - 1] ?? false;
|
||||
if (!$lastelement) {
|
||||
$count = $lastelement[0] ?? 1;
|
||||
}
|
||||
$createddate = serverdatetimesql();
|
||||
$modifieddate = serverdatetimesql();
|
||||
|
||||
$userdetails = DBQUERY()->USERS()->GetUserDatabyUID($useruid);
|
||||
if (!$userdetails) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$userSUID = $userdetails['uid'];
|
||||
$userHash = $userdetails['hashkey'];
|
||||
|
||||
$reviews[] = [$count, $createddate, $modifieddate, $userHash, $rating, $content];
|
||||
return $this->setReviewsbyUID($uidorhashkey, $reviews);
|
||||
|
||||
}
|
||||
function clearReviewsbyUID($uidorhashkey)
|
||||
{
|
||||
return $this->setReviewsbyUID($uidorhashkey, []);
|
||||
}
|
||||
|
||||
function listProductsbyStatus($status = null, $fieldstoselectarray = '', )
|
||||
{
|
||||
return DBQUERY()->PRODUCTS()->ListFromDB(['status' => strtolower($status)], $likefields = [], $fieldstoselectarray) ?? false;
|
||||
}
|
||||
function listActiveProductsStatus($fieldstoselectarray = '')
|
||||
{
|
||||
return $this->listProductsbyStatus('active', $fieldstoselectarray);
|
||||
}
|
||||
function listProductsbyStatuswithStock($status = null, $fieldstoselectarray = '')
|
||||
{
|
||||
|
||||
$noavailableflag = 0;
|
||||
if ($fieldstoselectarray && !in_array('available', $fieldstoselectarray)) {
|
||||
$noavailableflag = 1;
|
||||
$fieldstoselectarray[] = 'available';
|
||||
}
|
||||
$list = $this->listProductsbyStatus($status, $fieldstoselectarray);
|
||||
if (!$list) {
|
||||
return false;
|
||||
}
|
||||
foreach ($list as $key => $value) {
|
||||
if ($value['available'] < 1) {
|
||||
unset($list[$key]);
|
||||
continue;
|
||||
}
|
||||
if ($noavailableflag) {
|
||||
unset($list[$key]['available']);
|
||||
}
|
||||
|
||||
}
|
||||
$list = array_values($list);
|
||||
return $list;
|
||||
}
|
||||
function listAvailableandActiveProducts($fieldstoselectarray = '')
|
||||
{
|
||||
return $this->listProductsbyStatuswithStock('active', $fieldstoselectarray);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class DB_PRODUCTS_QUICK_MULTIPLESEARCH
|
||||
{
|
||||
use DBClassSearch;
|
||||
public $data;
|
||||
|
||||
public $tablename = 'products';
|
||||
|
||||
private $parentidresults = [];
|
||||
|
||||
|
||||
public function __construct($data = [], $likefields = [], $fieldstoselectarray = '', $orderby = '', $noindex = 0, $whereappend = ' and ', $dateonlyarray = '', $newdata = false)
|
||||
{
|
||||
return $this->initialize($data, $likefields, $fieldstoselectarray, $orderby, $noindex, $whereappend, $dateonlyarray, $newdata);
|
||||
}
|
||||
|
||||
function FindName($uid)
|
||||
{
|
||||
return $this->Find($fieldname = 'uid', $contenttosearch = $uid, $exact = true)[0]['name'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function DB_PRODUCTS_QUICK_MULTIPLESEARCH($DB = false)
|
||||
{
|
||||
return new DB_PRODUCTS_QUICK_MULTIPLESEARCH($DB);
|
||||
}
|
||||
|
||||
function DB_PRODUCTS($DB = false)
|
||||
{
|
||||
return new DB_PRODUCTS($DB);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
|
||||
class DB_PRODUCTSHISTORY
|
||||
{
|
||||
public $DB = false;
|
||||
public $tablename = 'productshistory';
|
||||
|
||||
use BASICDB;
|
||||
public function __construct($DB = false)
|
||||
{
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
if (!$DB) {
|
||||
return false;
|
||||
}
|
||||
if ($DB) {
|
||||
$this->DB = $DB;
|
||||
}
|
||||
if (!$this->tablename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
function NewProductHistory($data)
|
||||
{
|
||||
$required = ['available', 'price', 'name', 'description', 'status', 'storeuid'];
|
||||
return $this->DefaultDBInsert($data, $required);
|
||||
}
|
||||
}
|
||||
|
||||
class DB_PRODUCTSHISTORY_QUICK_MULTIPLESEARCH
|
||||
{
|
||||
use DBClassSearch;
|
||||
public $data;
|
||||
|
||||
public $tablename = 'productshistory';
|
||||
|
||||
private $parentidresults = [];
|
||||
|
||||
|
||||
public function __construct($data = [], $likefields = [], $fieldstoselectarray = '', $orderby = '', $noindex = 0, $whereappend = ' and ', $dateonlyarray = '', $newdata = false)
|
||||
{
|
||||
return $this->initialize($data, $likefields, $fieldstoselectarray, $orderby, $noindex, $whereappend, $dateonlyarray, $newdata);
|
||||
}
|
||||
|
||||
function FindName($uid)
|
||||
{
|
||||
return $this->Find($fieldname = 'uid', $contenttosearch = $uid, $exact = true)[0]['name'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function DB_PRODUCTSHISTORY($DB = false)
|
||||
{
|
||||
return new DB_PRODUCTSHISTORY($DB);
|
||||
}
|
||||
function DB_PRODUCTSHISTORY_QUICK_MULTIPLESEARCH($DB = false)
|
||||
{
|
||||
return new DB_PRODUCTSHISTORY_QUICK_MULTIPLESEARCH($DB);
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
|
||||
class DB_STORES
|
||||
{
|
||||
public $DB = false;
|
||||
public $tablename = 'stores';
|
||||
|
||||
use BASICDB;
|
||||
public function __construct($DB = false)
|
||||
{
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
if (!$DB) {
|
||||
return false;
|
||||
}
|
||||
if ($DB) {
|
||||
$this->DB = $DB;
|
||||
}
|
||||
if (!$this->tablename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function GenerateStoreCode($category, $num=null, $length = 8, )
|
||||
{
|
||||
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_';
|
||||
$charactersLength = strlen($characters);
|
||||
$storeCode = '';
|
||||
if(!$num){
|
||||
$num = rand(1,99);
|
||||
}
|
||||
|
||||
// Generate a random alphanumeric string
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$storeCode .= $characters[rand(0, $charactersLength - 1)];
|
||||
}
|
||||
$categoryPrefix = substr($category, 0, 2);
|
||||
$categorySuffix = substr($category, -2, 2);
|
||||
$cat = $categoryPrefix . $categorySuffix;
|
||||
$storeCode = date('Y') . '-' . $cat . '-' . $num . '-' . $storeCode;
|
||||
return $storeCode;
|
||||
}
|
||||
|
||||
private function generateStoreCodeUnique($storeCodeArray, $category, $num = '', $length = 8)
|
||||
{
|
||||
$storeCodeUnique = false;
|
||||
|
||||
while (!$storeCodeUnique) {
|
||||
$newstorecode = $this->GenerateStoreCode($category, $num, $length = 8);
|
||||
if (!in_array(strtoupper($storeCodeUnique), $storeCodeArray)) {
|
||||
$storeCodeUnique = $newstorecode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $newstorecode;
|
||||
}
|
||||
function generateNewStoreCodefromDB($category, $num = '', $length = 8)
|
||||
{
|
||||
$storecodes = $this->ListFromDB([], [], ['storecode']);
|
||||
if (!$storecodes) {
|
||||
$storecodes = [];
|
||||
}
|
||||
$storecodes = array_column($storecodes, 'storecode');
|
||||
$targetdateYEARMONTH = date('Y') . '-' . date('m');
|
||||
$datecreated = $this->ListbyDateCreated($targetdateYEARMONTH);
|
||||
$newnum = (count($datecreated)) + 1;
|
||||
if (!$num) {
|
||||
$num = $targetdateYEARMONTH . '-' . $newnum;
|
||||
}
|
||||
$uniqueStoreCode = $this->generateStoreCodeUnique($storecodes, $category, $num, $length);
|
||||
return $uniqueStoreCode;
|
||||
}
|
||||
|
||||
function NewStore($name, $description, $owneruid, $manageruid, $storeAddress, $category = '', $subcategory = '', $photourl = '', $storecode = '', $status = 'active', $specs = '', $additionaldata = '', $remarks = '', $createdby = '')
|
||||
{
|
||||
|
||||
if (!$name || !$description || !$owneruid || !$manageruid || !$storeAddress) {
|
||||
return false;
|
||||
}
|
||||
$GetUserDetails = function ($useruid) {
|
||||
if (!$useruid) {
|
||||
return false;
|
||||
}
|
||||
$details = DBQUERY()->USERS()->GetUserDatabyUID($useruid);
|
||||
return $details;
|
||||
};
|
||||
|
||||
$addtoDataifNotEmpty = function ($varname, $varvalue) use (&$data) {
|
||||
if (!$varname || !$varvalue) {
|
||||
return false;
|
||||
}
|
||||
if (is_array($varvalue)) {
|
||||
$varvalue = tryjsonencode($varvalue);
|
||||
}
|
||||
$data[$varname] = $varvalue;
|
||||
};
|
||||
|
||||
global $CurrentUserUID;
|
||||
if (!$createdby) {
|
||||
$createdby = $CurrentUserUID;
|
||||
}
|
||||
if (!$createdby) {
|
||||
$createdby = CurrentUserUID();
|
||||
}
|
||||
|
||||
$createdby_details = DB_USERS()->getDetailsbyUIDorHashkey($createdby) ?? false;
|
||||
if (!$createdby_details) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$owner_details = $GetUserDetails($owneruid);
|
||||
if (!$owner_details) {
|
||||
return false;
|
||||
}
|
||||
$manager_details = $GetUserDetails($manageruid);
|
||||
if (!$manager_details) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!MARKET_MAIN()->isNewTransactionAllowed($createdby_details, $manager_details, $owner_details)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (!$storecode) {
|
||||
$storecode = $this->generateNewStoreCodefromDB($category);
|
||||
}
|
||||
|
||||
$datetimenow = serverdatetimesql();
|
||||
$hashkey = $this->GenerateNewHash();
|
||||
$logs = [$datetimenow, 'Created New Store by useruid(' . $createdby . ') at ' . $datetimenow];
|
||||
$data = [
|
||||
'hashkey' => $hashkey,
|
||||
'created' => $datetimenow,
|
||||
'modified' => $datetimenow,
|
||||
'logs' => $logs,
|
||||
'name' => $name,
|
||||
'description' => $description,
|
||||
'owneruid' => $owneruid,
|
||||
'manageruid' => $manageruid,
|
||||
'createdby' => $createdby,
|
||||
'address' => $storeAddress
|
||||
];
|
||||
|
||||
|
||||
$addtoDataifNotEmpty('category', $category);
|
||||
$addtoDataifNotEmpty('subcategory', $subcategory);
|
||||
$addtoDataifNotEmpty('photourl', $photourl);
|
||||
$addtoDataifNotEmpty('storecode', $storecode);
|
||||
$addtoDataifNotEmpty('status', $status);
|
||||
$addtoDataifNotEmpty('specs', $specs);
|
||||
$addtoDataifNotEmpty('additionaldata', $additionaldata);
|
||||
$addtoDataifNotEmpty('remarks', $remarks);
|
||||
|
||||
$insert = $this->InsertIntoDB($data);
|
||||
return $insert;
|
||||
}
|
||||
}
|
||||
|
||||
class DB_STORES_QUICK_MULTIPLESEARCH
|
||||
{
|
||||
use DBClassSearch;
|
||||
public $data;
|
||||
|
||||
public $tablename = 'stores';
|
||||
|
||||
private $parentidresults = [];
|
||||
|
||||
|
||||
public function __construct($data = [], $likefields = [], $fieldstoselectarray = '', $orderby = '', $noindex = 0, $whereappend = ' and ', $dateonlyarray = '', $newdata = false)
|
||||
{
|
||||
return $this->initialize($data, $likefields, $fieldstoselectarray, $orderby, $noindex, $whereappend, $dateonlyarray, $newdata);
|
||||
}
|
||||
|
||||
function FindName($uid)
|
||||
{
|
||||
return $this->Find($fieldname = 'uid', $contenttosearch = $uid, $exact = true)[0]['name'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function DB_STORES($DB = false)
|
||||
{
|
||||
return new DB_STORES($DB);
|
||||
}
|
||||
|
||||
function DB_STORES_QUICK_MULTIPLESEARCH($DB = false)
|
||||
{
|
||||
return new DB_STORES_QUICK_MULTIPLESEARCH($DB);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
|
||||
|
||||
class DB_SUPPLIERS
|
||||
{
|
||||
public $DB = false;
|
||||
public $tablename = 'suppliers';
|
||||
|
||||
use BASICDB;
|
||||
public function __construct($DB = false)
|
||||
{
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
if (!$DB) {
|
||||
return false;
|
||||
}
|
||||
if ($DB) {
|
||||
$this->DB = $DB;
|
||||
}
|
||||
if (!$this->tablename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
function NewSupplier()
|
||||
{
|
||||
|
||||
$data = [
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
];
|
||||
|
||||
|
||||
return $this->InsertBasicDBHashCreatedModified($data);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class DB_SUPPLIERS_QUICK_MULTIPLESEARCH
|
||||
{
|
||||
use DBClassSearch;
|
||||
public $data;
|
||||
|
||||
public $tablename = 'suppliers';
|
||||
|
||||
private $parentidresults = [];
|
||||
|
||||
|
||||
public function __construct($data = [], $likefields = [], $fieldstoselectarray = '', $orderby = '', $noindex = 0, $whereappend = ' and ', $dateonlyarray = '', $newdata = false)
|
||||
{
|
||||
return $this->initialize($data, $likefields, $fieldstoselectarray, $orderby, $noindex, $whereappend, $dateonlyarray, $newdata);
|
||||
}
|
||||
|
||||
function FindName($uid)
|
||||
{
|
||||
return $this->Find($fieldname = 'uid', $contenttosearch = $uid, $exact = true)[0]['name'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function DB_SUPPLIERS($DB = false)
|
||||
{
|
||||
return new DB_SUPPLIERS($DB);
|
||||
}
|
||||
|
||||
function DB_SUPPLIERS_QUICK_MULTIPLESEARCH($DB = false)
|
||||
{
|
||||
return new DB_SUPPLIERS_QUICK_MULTIPLESEARCH($DB);
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
<?php
|
||||
|
||||
$classMap['PROPERTIES'] = 'DB_PROPERTIES';
|
||||
$classMap['REFERRALS'] = 'DB_REFERRALS';
|
||||
$classMap['REFERRAL_KEYS'] = 'DB_REFERRAL_KEYS';
|
||||
|
||||
class DB_REFERRALS
|
||||
{
|
||||
|
||||
public $DB = false;
|
||||
public $tablename = 'referrals';
|
||||
public $logs;
|
||||
public $status;
|
||||
use BASICDB;
|
||||
use LOGSDB;
|
||||
use STATUSDB;
|
||||
public function __construct($DB = false)
|
||||
{
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
if (!$DB) {
|
||||
return false;
|
||||
}
|
||||
if ($DB) {
|
||||
$this->DB = $DB;
|
||||
}
|
||||
|
||||
$this->logs = new stdClass();
|
||||
$this->logs->setLogbyUID = [$this, 'setLogbyUID'];
|
||||
$this->logs->deleteFullLogbyUID = [$this, 'deleteFullLogbyUID'];
|
||||
$this->logs->viewLogsbyUID = [$this, 'viewLogsbyUID'];
|
||||
$this->logs->deleteLogbyArrayIndex = [$this, 'deleteLogbyArrayIndex'];
|
||||
$this->logs->AddLog = [$this, 'AddLog'];
|
||||
$this->status = new stdClass();
|
||||
$this->status->UpdateStatus = [$this, 'UpdateStatus'];
|
||||
$this->status->ViewStatus = [$this, 'ViewStatus'];
|
||||
$this->status->SetStatusAsDenied = [$this, 'SetStatusAsDenied'];
|
||||
$this->status->SetStatusAsOngoing = [$this, 'SetStatusAsOngoing'];
|
||||
$this->status->SetStatusAsNULL = [$this, 'SetStatusAsNULL'];
|
||||
$this->status->SetStatusAsNULL = [$this, 'SetStatusAsApproved'];
|
||||
|
||||
}
|
||||
function NewReferral($fullname, $mobile, $landline = '', $facebookurl = '', $email = '', $property_location = '', $target_property = '', $target_viewing_date = '', $firstname = '', $middlename = '', $lastname = '', $referral_key = 0, $remarks = '', $alt_contact_details = '', $status = 0)
|
||||
{
|
||||
|
||||
if (!$fullname || !$mobile) {
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!$referral_key) {
|
||||
$referral_key = CurrentUserUID();
|
||||
}
|
||||
if (!$referral_key) {
|
||||
$referral_key = '';
|
||||
}
|
||||
$target_property = $target_property ?? 0;
|
||||
$hash = $this->GenerateNewHash();
|
||||
|
||||
if (!$alt_contact_details) {
|
||||
$alt_contact_details = '[]';
|
||||
} elseif (is_array($alt_contact_details)) {
|
||||
$alt_contact_details = json_encode($alt_contact_details);
|
||||
}
|
||||
$data = [];
|
||||
|
||||
$ConvertToJSONifPossible = function ($datakey, $dataarraay) use (&$data) {
|
||||
if (!$datakey) {
|
||||
return;
|
||||
}
|
||||
if ($dataarraay) {
|
||||
if (is_array($dataarraay)) {
|
||||
$data[$datakey] = json_encode($dataarraay);
|
||||
} else {
|
||||
$data[$datakey] = $dataarraay;
|
||||
}
|
||||
} else {
|
||||
$data[$datakey] = '[]';
|
||||
}
|
||||
};
|
||||
$ifNotNullAddtoArray = function ($datakey, $dataval) use (&$data) {
|
||||
if (!$dataval) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data[$datakey] = $dataval;
|
||||
};
|
||||
|
||||
// file_put_contents('results.txt', print_r($email, 1) . print_r($landline, 1));
|
||||
|
||||
//fix target_property in DB
|
||||
|
||||
$data = [
|
||||
'hashkey' => $hash,
|
||||
'fullname' => $fullname,
|
||||
'mobile' => $mobile,
|
||||
'referral_key' => $referral_key,
|
||||
'email' => $email ?? '',
|
||||
];
|
||||
|
||||
if ($target_property && !is_numeric($target_property)) {
|
||||
$target_property = DBQUERY($this->DB)->PROPERTIES()->getUIDfromHashkey($target_property) ?? 0;
|
||||
}
|
||||
|
||||
$ifNotNullAddtoArray('referral_key', $referral_key);
|
||||
$ifNotNullAddtoArray('landline', $landline);
|
||||
$ifNotNullAddtoArray('target_property', $target_property);
|
||||
$ifNotNullAddtoArray('property_location', $property_location);
|
||||
|
||||
|
||||
$ifNotNullAddtoArray('facebookurl', $facebookurl);
|
||||
$ifNotNullAddtoArray('firstname', $firstname);
|
||||
$ifNotNullAddtoArray('middlename', $middlename);
|
||||
$ifNotNullAddtoArray('lastname', $lastname);
|
||||
$ifNotNullAddtoArray('remarks', $remarks);
|
||||
$ifNotNullAddtoArray('target_viewing_date', $target_viewing_date);
|
||||
|
||||
|
||||
|
||||
$ConvertToJSONifPossible('alt_contact_details', $alt_contact_details);
|
||||
|
||||
|
||||
|
||||
|
||||
if ($target_viewing_date) {
|
||||
$data['target_viewing_date'] = $target_viewing_date;
|
||||
}
|
||||
return $this->InsertIntoDB($data);
|
||||
|
||||
}
|
||||
|
||||
public function SetStatusAsDenied($uidorhashkey)
|
||||
{
|
||||
return $this->status->UpdateStatus($uidorhashkey, -1);
|
||||
}
|
||||
public function SetStatusAsOngoing($uidorhashkey)
|
||||
{
|
||||
return $this->status->UpdateStatus($uidorhashkey, 1);
|
||||
}
|
||||
public function SetStatusAsNULL($uidorhashkey)
|
||||
{
|
||||
return $this->status->UpdateStatus($uidorhashkey, 0);
|
||||
}
|
||||
public function SetStatusAsApproved($uidorhashkey)
|
||||
{
|
||||
return $this->status->UpdateStatus($uidorhashkey, 2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
function DB_REFERRALS($DB = false)
|
||||
{
|
||||
return new DB_REFERRALS($DB);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
class REFERRALS_QUICKMULTIPLESEARCH
|
||||
{
|
||||
|
||||
use DBClassSearch;
|
||||
public $data;
|
||||
|
||||
public $tablename = 'referrals';
|
||||
|
||||
private $parentidresults = [];
|
||||
|
||||
public $DB;
|
||||
public function __construct($data = [], $likefields = [], $fieldstoselectarray = '', $orderby = '', $noindex = 0, $whereappend = ' and ', $dateonlyarray = '', $newdata = false)
|
||||
{
|
||||
return $this->initialize($data, $likefields, $fieldstoselectarray, $orderby, $noindex, $whereappend, $dateonlyarray, $newdata);
|
||||
}
|
||||
|
||||
function FindbyExactFullName($name, $newdata = false)
|
||||
{
|
||||
return $this->Find('fullname', $name, $exact = true, true, $newdata)['name'];
|
||||
}
|
||||
function ListbySimilarNames($name, $newdata = false, $usestrpos = false)
|
||||
{
|
||||
return $this->List('fullname', $name, false, true, $newdata, $usestrpos);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function REFERRALS_QUICKMULTIPLESEARCH($DB = false)
|
||||
{
|
||||
return new REFERRALS_QUICKMULTIPLESEARCH($DB);
|
||||
}
|
||||
|
||||
|
||||
class DB_PROPERTIES
|
||||
{
|
||||
|
||||
public $DB = false;
|
||||
public $tablename = 'properties';
|
||||
|
||||
use BASICDB;
|
||||
use LOGSDB;
|
||||
use STATUSDB;
|
||||
public function __construct($DB = false)
|
||||
{
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
if (!$DB) {
|
||||
return false;
|
||||
}
|
||||
if ($DB) {
|
||||
$this->DB = $DB;
|
||||
}
|
||||
if (!$this->tablename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
function NewProperties($name, $description, $location, $photourl, $sqm = '', $rooms = NULL, $bedrooms = '', $kitchen = '', $toilet = '', $floors = '', $specs = '', $category = '', $subcategory = '', $status = 0, $remarks = '', $forbiddenUIDsArray = '', $createdby = '', $price = '')
|
||||
{
|
||||
if (!$name || !$description || !$location || !$photourl) {
|
||||
return false;
|
||||
}
|
||||
if ($forbiddenUIDsArray and is_array($forbiddenUIDsArray)) {
|
||||
$forbiddenUIDsArray = json_encode($forbiddenUIDsArray);
|
||||
}
|
||||
$data = [];
|
||||
|
||||
$ConvertToJSONifPossible = function ($datakey, $dataarraay) use (&$data) {
|
||||
if (!$datakey) {
|
||||
return;
|
||||
}
|
||||
if ($dataarraay) {
|
||||
if (is_array($dataarraay)) {
|
||||
$data[$datakey] = json_encode($dataarraay);
|
||||
} else {
|
||||
$data[$datakey] = $dataarraay;
|
||||
}
|
||||
} else {
|
||||
$data[$datakey] = '[]';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
$ifNotNullAddtoArray = function ($datakey, $dataval) use (&$data) {
|
||||
if (!$dataval) {
|
||||
return;
|
||||
}
|
||||
$data[$datakey] = $dataval;
|
||||
};
|
||||
|
||||
$hashkey = $this->GenerateNewHash();
|
||||
|
||||
if (!$createdby) {
|
||||
$createdby = CurrentUserUID();
|
||||
}
|
||||
if ($createdby) {
|
||||
$data['createdby'] = $createdby;
|
||||
}
|
||||
$data['hashkey'] = $hashkey;
|
||||
$data['name'] = $name;
|
||||
$data['description'] = $description;
|
||||
$data['location'] = $location;
|
||||
$ConvertToJSONifPossible('photourl', $photourl);
|
||||
$ifNotNullAddtoArray('sqm', $sqm);
|
||||
$ifNotNullAddtoArray('rooms', $rooms);
|
||||
$ifNotNullAddtoArray('bedrooms', $bedrooms);
|
||||
$ifNotNullAddtoArray('kitchen', $kitchen);
|
||||
$ifNotNullAddtoArray('toilet', $toilet);
|
||||
$ifNotNullAddtoArray('floors', $floors);
|
||||
$ifNotNullAddtoArray('category', $category);
|
||||
$ifNotNullAddtoArray('subcategory', $subcategory);
|
||||
$ifNotNullAddtoArray('price', $price);
|
||||
$ConvertToJSONifPossible('specs', $specs);
|
||||
|
||||
$data['status'] = $status ?? 0;
|
||||
$data['remarks'] = $remarks ?? '';
|
||||
$data['forbiddenuids'] = $forbiddenUIDsArray ?? '';
|
||||
$data['logs'] = '[["' . serverdatetimesql() . '","Created Property"]]';
|
||||
|
||||
$Insert = $this->InsertIntoDB($data);
|
||||
return $Insert;
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
function DB_PROPERTIES($DB = false)
|
||||
{
|
||||
return new DB_PROPERTIES($DB);
|
||||
}
|
||||
|
||||
class PROPERTIES_QUICKMULTIPLESEARCH
|
||||
{
|
||||
|
||||
use DBClassSearch;
|
||||
public $data;
|
||||
|
||||
public $tablename = 'properties';
|
||||
|
||||
private $parentidresults = [];
|
||||
public $DB;
|
||||
|
||||
public function __construct($data = [], $likefields = [], $fieldstoselectarray = '', $orderby = '', $noindex = 0, $whereappend = ' and ', $dateonlyarray = '', $newdata = false)
|
||||
{
|
||||
return $this->initialize($data, $likefields, $fieldstoselectarray, $orderby, $noindex, $whereappend, $dateonlyarray, $newdata);
|
||||
}
|
||||
|
||||
function FindName($uid)
|
||||
{
|
||||
return $this->Find($fieldname = 'uid', $contenttosearch = $uid, $exact = true)[0]['name'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function PROPERTIES_QUICKMULTIPLESEARCH($DB = false)
|
||||
{
|
||||
return new PROPERTIES_QUICKMULTIPLESEARCH($DB);
|
||||
}
|
||||
|
||||
class DB_REFERRAL_KEYS
|
||||
{
|
||||
|
||||
public $DB = false;
|
||||
public $tablename = 'referral_keys';
|
||||
|
||||
use BASICDB;
|
||||
public function __construct($DB = false)
|
||||
{
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
if (!$DB) {
|
||||
return false;
|
||||
}
|
||||
if ($DB) {
|
||||
$this->DB = $DB;
|
||||
}
|
||||
if (!$this->tablename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
function NewReferralKey($useruid = '', $propertyuid = 0, $referral_code = 0)
|
||||
{
|
||||
if (!$useruid) {
|
||||
$useruid = CurrentUserUID();
|
||||
}
|
||||
if (!$useruid) {
|
||||
$useruid = '';
|
||||
}
|
||||
if ($useruid and is_numeric($useruid)) {
|
||||
$useruid = DB_USERS($this->DB)->GetUserDatabyUID($useruid)['uid'] ?? '';
|
||||
}
|
||||
$hashkey = $this->GenerateNewHash();
|
||||
$data['hashkey'] = $hashkey;
|
||||
$data['useruid'] = $useruid ?? 0;
|
||||
$data['propertyuid'] = $propertyuid ?? 0;
|
||||
|
||||
|
||||
|
||||
if ($referral_code) {
|
||||
$referral_code_exists = $this->getDetailsbyReferral_Key($referral_code);
|
||||
if ($referral_code_exists) {
|
||||
return false;
|
||||
}
|
||||
$data['referral_code'] = $referral_code;
|
||||
} else {
|
||||
$data['referral_code'] = generateUniqueReferralCode($this->DB, 'referral_code', $this->tablename);
|
||||
}
|
||||
|
||||
$Insert = $this->InsertIntoDB($data);
|
||||
return $Insert;
|
||||
}
|
||||
function getDetailsbyReferral_Key($referral_code, $fieldstoselect = '')
|
||||
{
|
||||
if (!$referral_code) {
|
||||
return false;
|
||||
}
|
||||
$details = $this->ListFromDB(['referral_code' => $referral_code], [], $fieldstoselect);
|
||||
return $details[0] ?? false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
function DB_REFERRAL_KEYS($DB = false)
|
||||
{
|
||||
return new DB_REFERRAL_KEYS($DB);
|
||||
}
|
||||
|
||||
class REFERRAL_KEYS_QUICKMULTIPLESEARCH
|
||||
{
|
||||
|
||||
use DBClassSearch;
|
||||
public $data;
|
||||
|
||||
public $tablename = 'referral_keys';
|
||||
|
||||
private $parentidresults = [];
|
||||
public $DB;
|
||||
|
||||
public function __construct($data = [], $likefields = [], $fieldstoselectarray = '', $orderby = '', $noindex = 0, $whereappend = ' and ', $dateonlyarray = '', $newdata = false, $DB = false)
|
||||
{
|
||||
return $this->initialize($data, $likefields, $fieldstoselectarray, $orderby, $noindex, $whereappend, $dateonlyarray, $newdata, $DB);
|
||||
}
|
||||
|
||||
function FindName($uid)
|
||||
{
|
||||
return $this->Find($fieldname = 'uid', $contenttosearch = $uid, $exact = true)[0]['name'];
|
||||
}
|
||||
function GetReferralCodebyUID($uidorhashkey, $newdata = false)
|
||||
{
|
||||
return $this->getValueByUIDorHashkey($uidorhashkey, 'referral_code', $newdata);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function REFERRAL_KEYS_QUICKMULTIPLESEARCH($DB = false)
|
||||
{
|
||||
return new REFERRAL_KEYS_QUICKMULTIPLESEARCH($DB);
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
|
||||
class DB_TABLENAME
|
||||
{
|
||||
public $DB = false;
|
||||
public $tablename = 'users';
|
||||
|
||||
use BASICDB;
|
||||
public function __construct($DB = false)
|
||||
{
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
if (!$DB) {
|
||||
return false;
|
||||
}
|
||||
if ($DB) {
|
||||
$this->DB = $DB;
|
||||
}
|
||||
if (!$this->tablename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
function NewRecord()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class DB_TABLENAME_QUICK_MULTIPLESEARCH
|
||||
{
|
||||
use DBClassSearch;
|
||||
public $data;
|
||||
|
||||
public $tablename = 'tablename';
|
||||
|
||||
private $parentidresults = [];
|
||||
|
||||
|
||||
public function __construct($data = [], $likefields = [], $fieldstoselectarray = '', $orderby = '', $noindex = 0, $whereappend = ' and ', $dateonlyarray = '', $newdata = false)
|
||||
{
|
||||
return $this->initialize($data, $likefields, $fieldstoselectarray, $orderby, $noindex, $whereappend, $dateonlyarray, $newdata);
|
||||
}
|
||||
|
||||
function FindName($uid)
|
||||
{
|
||||
return $this->Find($fieldname = 'uid', $contenttosearch = $uid, $exact = true)[0]['name'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function DB_TABLENAME($DB=false){
|
||||
return new DB_TABLENAME($DB);
|
||||
}
|
||||
function DB_TABLENAME_QUICK_MULTIPLESEARCH($DB=false){
|
||||
return new DB_TABLENAME_QUICK_MULTIPLESEARCH($DB);
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
306
app/Http/Controllers/Helpers/Legacy/Backup/DBEXT/USERINFODB.php
Normal file
306
app/Http/Controllers/Helpers/Legacy/Backup/DBEXT/USERINFODB.php
Normal file
@@ -0,0 +1,306 @@
|
||||
<?php
|
||||
class DB_USERINFO
|
||||
{
|
||||
public $DB = false;
|
||||
public $tablename = 'userinfo';
|
||||
|
||||
use BASICDB;
|
||||
public function __construct($DB = false)
|
||||
{
|
||||
if (!$DB) {
|
||||
$DB = DB();
|
||||
}
|
||||
if (!$DB) {
|
||||
return false;
|
||||
}
|
||||
if ($DB) {
|
||||
$this->DB = $DB;
|
||||
}
|
||||
if (!$this->tablename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
function NewUserInfo($targetuserlogin, $fullname = '', $firstname = '', $middlename = '', $lastname = '', $landline = '', $mobile = '', $email = '', $altemail = '', $altlandline = '', $altmobile = '', $credit_card = '', $bankdetails = '[]', $facebookurl = '', $photourl = '[]', $otherdetails = '[]', $addresses = '[]')
|
||||
{
|
||||
if (!$targetuserlogin) {
|
||||
return false;
|
||||
}
|
||||
$usercheck = DBQUERY()->USERS()->getDetailsbyUIDorHashkey($targetuserlogin)['uid'] ?? false;
|
||||
if (!$usercheck) {
|
||||
return false;
|
||||
}
|
||||
$hashkey = $this->GenerateNewHash();
|
||||
if ($bankdetails) {
|
||||
if (is_array($bankdetails)) {
|
||||
$bankdetails = json_encode($bankdetails);
|
||||
}
|
||||
} else {
|
||||
$bankdetails = '[]';
|
||||
}
|
||||
if ($otherdetails) {
|
||||
if (is_array($otherdetails)) {
|
||||
$otherdetails = json_encode($otherdetails);
|
||||
}
|
||||
} else {
|
||||
$otherdetails = '[]';
|
||||
}
|
||||
if ($addresses) {
|
||||
if (is_array($otherdetails)) {
|
||||
$otherdetails = json_encode($otherdetails);
|
||||
}
|
||||
} else {
|
||||
$addresses = '[]';
|
||||
}
|
||||
|
||||
|
||||
$data = [
|
||||
'hashkey' => $hashkey,
|
||||
'targetuserlogin' => $targetuserlogin,
|
||||
'fullname' => $fullname,
|
||||
'firstname' => $firstname,
|
||||
'middlename' => $middlename,
|
||||
'lastname' => $lastname,
|
||||
'landline' => $landline,
|
||||
'mobile' => $mobile,
|
||||
'email' => $email,
|
||||
'altemail' => $altemail,
|
||||
'altlandline' => $altlandline,
|
||||
'altmobile' => $altmobile,
|
||||
'credit_card' => $credit_card,
|
||||
'bankdetails' => $bankdetails,
|
||||
'facebookurl' => $facebookurl,
|
||||
'photourl' => $photourl,
|
||||
'otherdetails' => $otherdetails,
|
||||
'addresses' => $addresses
|
||||
|
||||
];
|
||||
$insert = $this->InsertIntoDB($data);
|
||||
return $insert;
|
||||
}
|
||||
function GetbyTargetUserUID($useruidorhashkey, $fieldstoselect = '')
|
||||
{
|
||||
$result = $this->ListbyTargetUserUID($useruidorhashkey, $fieldstoselect);
|
||||
|
||||
return $result[0] ?? [];
|
||||
}
|
||||
function ListbyTargetUserUID($useruidorhashkey, $fieldstoselect = '')
|
||||
{
|
||||
if (!$useruidorhashkey) {
|
||||
return false;
|
||||
}
|
||||
if (!is_numeric($useruidorhashkey)) {
|
||||
$useruidorhashkey = DB_USERS($this->DB)->GetUserUIDbyHashkey($useruidorhashkey) ?? false;
|
||||
}
|
||||
|
||||
if (!$useruidorhashkey || !is_numeric($useruidorhashkey)) {
|
||||
return false;
|
||||
}
|
||||
return $this->ListFromDB(['targetuserlogin' => $useruidorhashkey], [], $fieldstoselect);
|
||||
}
|
||||
|
||||
function ModifybyTargetUserUID($targetuserUID, $data)
|
||||
{
|
||||
if (!$targetuserUID || !$data) {
|
||||
return false;
|
||||
}
|
||||
$wherearray = ['targetuserlogin' => $targetuserUID];
|
||||
return $this->ModifySingleRowwithVerification($data, $wherearray);
|
||||
}
|
||||
function GetAddresses($targetuserUID)
|
||||
{
|
||||
if (!$targetuserUID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$details = $this->GetbyTargetUserUID($targetuserUID);
|
||||
if (!$details) {
|
||||
return false;
|
||||
}
|
||||
$addresses = $details['addresses'] ?? null;
|
||||
if ($addresses) {
|
||||
$addresses = tryjsondecode($addresses, true);
|
||||
}
|
||||
return $addresses;
|
||||
}
|
||||
function GetLastAddress($targetuserUID)
|
||||
{
|
||||
$details = $this->GetAddresses($targetuserUID);
|
||||
if ($details === false) {
|
||||
return false;
|
||||
}
|
||||
if ($details === null) {
|
||||
return null;
|
||||
}
|
||||
$details[count($details) - 1]['key'] = count($details) - 1;
|
||||
return $details[count($details) - 1];
|
||||
}
|
||||
function getDefaultAddress($targetuserUID)
|
||||
{
|
||||
if (!$targetuserUID) {
|
||||
return false;
|
||||
}
|
||||
$details = $this->GetAddresses($targetuserUID);
|
||||
if (!$details) {
|
||||
return false;
|
||||
}
|
||||
if (count($details) === 1) {
|
||||
$details['key'] = 0;
|
||||
return $details[0];
|
||||
}
|
||||
foreach ($details as $key => $value) {
|
||||
if ($value['default']) {
|
||||
$value['key'] = $key;
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function SetAddresses($targetuserUID, $newaddresses)
|
||||
{
|
||||
|
||||
if (!$targetuserUID || !is_numeric($targetuserUID)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = [];
|
||||
if (is_array($newaddresses)) {
|
||||
$newaddresses = array_values($newaddresses);
|
||||
$newaddresses = json_encode($newaddresses);
|
||||
}
|
||||
|
||||
$data['addresses'] = $newaddresses;
|
||||
return $this->ModifybyTargetUserUID($targetuserUID, $data);
|
||||
}
|
||||
function AddAddress($targetuserUID, $address, $addressname, $contactname, $contactnumber, $default = false)
|
||||
{
|
||||
if (!$targetuserUID || !$address || !$addressname || !$contactname || !$contactnumber) {
|
||||
return false;
|
||||
}
|
||||
if ($default === 'true') {
|
||||
$default = true;
|
||||
} elseif ($default === 'false') {
|
||||
$default = 0;
|
||||
}
|
||||
|
||||
$data = $this->GetAddresses($targetuserUID);
|
||||
if ($data === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$data) {
|
||||
$data = [];
|
||||
}
|
||||
if ($default) {
|
||||
foreach ($data as $key => $value) {
|
||||
$data[$key][4] = false;
|
||||
}
|
||||
}
|
||||
$data[] = [$address, $addressname, $contactname, $contactnumber, $default];
|
||||
|
||||
$submit = $this->SetAddresses($targetuserUID, $data);
|
||||
if (!$submit) {
|
||||
return false;
|
||||
}
|
||||
return $this->GetLastAddress($targetuserUID);
|
||||
}
|
||||
function RemoveAddress($targetuserUID, $index)
|
||||
{
|
||||
if (!$targetuserUID || !is_numeric($index)) {
|
||||
return false;
|
||||
}
|
||||
$addressees = $this->GetAddresses($targetuserUID);
|
||||
if ($addressees === false) {
|
||||
return false;
|
||||
}
|
||||
if (!isset($addressees[$index])) {
|
||||
return false;
|
||||
}
|
||||
unset($addressees[$index]);
|
||||
$addressees = array_values($addressees);
|
||||
return $this->SetAddresses($targetuserUID, $addressees);
|
||||
}
|
||||
function ModifyAddressbyIndex($targetuserUID, $index,$name=null,$address=null,$contactname=null,$contactnumber=null,$default=null)
|
||||
{
|
||||
if(!$name && !$address && !$contactname && !$contactnumber && $default===null) {return false;}
|
||||
if (!$targetuserUID || !is_numeric($index)) {
|
||||
return false;
|
||||
}
|
||||
$addressees = $this->GetAddresses($targetuserUID);
|
||||
if ($addressees === false) {
|
||||
return false;
|
||||
}
|
||||
if (!isset($addressees[$index])) {
|
||||
return false;
|
||||
}
|
||||
$dataname= $addressees[$index][1] ?? false;
|
||||
$dataaddress = $addressees[$index][0] ?? false;
|
||||
$datacontactname =$addressees[$index][2] ?? false;
|
||||
$datacontactnumber = $addressees[$index][3] ?? false;
|
||||
$datadefault = $addressees[$index][4] ?? false;
|
||||
|
||||
$finalname = $name ?? $dataname;
|
||||
$finaladdress = $address ?? $dataaddress;
|
||||
$finalcontactname = $contactname ?? $datacontactname;
|
||||
$finaldatacontactnumber =$contactnumber ?? $datacontactnumber;
|
||||
if($default !==null){
|
||||
$finaldefault = $default;
|
||||
}else{
|
||||
$finaldefault= $datadefault;
|
||||
}
|
||||
|
||||
if ($finaldefault) {
|
||||
foreach ($addressees as $key => $value) {
|
||||
$addressees[$key][4] = false;
|
||||
}
|
||||
}
|
||||
|
||||
$addressees[$index]=[$finaladdress,$finalname,$finalcontactname,$finaldatacontactnumber,$finaldefault];
|
||||
$addressees = array_values($addressees);
|
||||
return $this->SetAddresses($targetuserUID, $addressees);
|
||||
}
|
||||
function SetAddressAsDefault($targetuserUID,$index){
|
||||
return $this->ModifyAddressbyIndex($targetuserUID, $index,null,null,null,null,true);
|
||||
}
|
||||
function EmptyAddresses($targetuserUID)
|
||||
{
|
||||
return $this->SetAddresses($targetuserUID, '');
|
||||
}
|
||||
function ShowAddressFormat()
|
||||
{
|
||||
return ['Address', 'Address Name', 'Contact Name', 'Contact Number', 'default'];
|
||||
}
|
||||
}
|
||||
|
||||
function DB_USERINFO($db = false)
|
||||
{
|
||||
return new DB_USERINFO($db);
|
||||
}
|
||||
|
||||
function USERINFO_MULTIPLESEARCH($data = [], $likefields = [], $fieldstoselectarray = '', $orderby = '', $noindex = 0, $whereappend = ' and ', $dateonlyarray = '', $newdata = false)
|
||||
{
|
||||
|
||||
return new USERINFO_QUICK_MULTIPLESEARCH($data, $likefields, $fieldstoselectarray, $orderby, $noindex, $whereappend, $dateonlyarray, $newdata);
|
||||
}
|
||||
|
||||
class USERINFO_QUICK_MULTIPLESEARCH
|
||||
{
|
||||
use DBClassSearch;
|
||||
public $data;
|
||||
|
||||
public $tablename = 'users';
|
||||
|
||||
private $parentidresults = [];
|
||||
public $DB;
|
||||
|
||||
public function __construct($data = [], $likefields = [], $fieldstoselectarray = '', $orderby = '', $noindex = 0, $whereappend = ' and ', $dateonlyarray = '', $newdata = false)
|
||||
{
|
||||
return $this->initialize($data, $likefields, $fieldstoselectarray, $orderby, $noindex, $whereappend, $dateonlyarray, $newdata);
|
||||
}
|
||||
|
||||
function FindName($uid)
|
||||
{
|
||||
return $this->Find($fieldname = 'uid', $contenttosearch = $uid, $exact = true)[0]['name'];
|
||||
}
|
||||
|
||||
}
|
||||
91
app/Http/Controllers/Helpers/Legacy/Backup/Main.lib.php
Normal file
91
app/Http/Controllers/Helpers/Legacy/Backup/Main.lib.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
$altdb = 'u943309104_bukid';
|
||||
$altuser = 'u943309104_bukidb';
|
||||
$altpass = 'lB&3I=t6';
|
||||
$althost = 'mysql';
|
||||
$root_passDB = 'as73n1^1dhu';
|
||||
|
||||
$defaultAdminAppUser = '777';
|
||||
$defaultAdminAppPassword = 'OmegaPsilon32!';
|
||||
$InitializeDB = false;
|
||||
|
||||
$GLOBALS['defaultAdminAppUser'] = $defaultAdminAppUser;
|
||||
$GLOBALS['defaultAdminAppPassword'] =$defaultAdminAppPassword;
|
||||
|
||||
|
||||
function loadSettings()
|
||||
{
|
||||
$filePath = 'settings/settings.json';
|
||||
if (file_exists($filePath)) {
|
||||
$jsonContent = file_get_contents($filePath);
|
||||
|
||||
$MainSettings = json_decode($jsonContent, true);
|
||||
return $MainSettings;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$MainSettings = loadSettings();
|
||||
|
||||
if (!file_exists('settings/DBInitialized') && $InitializeDB) {
|
||||
RunDBInit();
|
||||
}
|
||||
|
||||
function DB()
|
||||
{
|
||||
global $althost;
|
||||
global $altdb;
|
||||
global $altuser;
|
||||
global $altpass;
|
||||
global $defaultAdminAppUser;
|
||||
global $defaultAdminAppPassword;
|
||||
|
||||
try {
|
||||
$db = opennewdb($altdb, $althost, $altuser, $altpass);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
try {
|
||||
$db = opennewdb('ta');
|
||||
} catch (Exception $le) {
|
||||
echo 'DB Error';
|
||||
exit;
|
||||
}
|
||||
|
||||
}
|
||||
return $db;
|
||||
}
|
||||
|
||||
|
||||
function RunDBInit()
|
||||
{
|
||||
global $althost;
|
||||
global $altdb;
|
||||
global $altuser;
|
||||
global $altpass;
|
||||
$mysqli = new mysqli($althost, $altuser, $altpass, $altdb);
|
||||
echo 'Initializing DB...' . PHP_EOL;
|
||||
if ($mysqli->connect_error) {
|
||||
echo 'Error...' . '<br>';
|
||||
die("Connection failed: " . $mysqli->connect_error);
|
||||
}
|
||||
echo 'Connected to DB...' . '<br>';
|
||||
|
||||
$query = file_get_contents("init/mysql.sql");
|
||||
|
||||
if ($mysqli->multi_query($query)) {
|
||||
do {
|
||||
|
||||
} while ($mysqli->more_results() && $mysqli->next_result());
|
||||
file_put_contents('settings/DBInitialized', 'true');
|
||||
echo "SQL imported successfully.".'<br>' ;
|
||||
} else {
|
||||
echo "Error importing SQL: " . $mysqli->error.'<br>';
|
||||
}
|
||||
}
|
||||
|
||||
require_once('lib.php');
|
||||
require_once('DB.php');
|
||||
require_once('lib/logins.php');
|
||||
1140
app/Http/Controllers/Helpers/Legacy/Backup/index.php
Normal file
1140
app/Http/Controllers/Helpers/Legacy/Backup/index.php
Normal file
File diff suppressed because it is too large
Load Diff
651
app/Http/Controllers/Helpers/Legacy/Backup/logins.php
Normal file
651
app/Http/Controllers/Helpers/Legacy/Backup/logins.php
Normal file
@@ -0,0 +1,651 @@
|
||||
<?php
|
||||
|
||||
|
||||
ini_set('session.cookie_lifetime', 315360000);
|
||||
ini_set('session.gc_maxlifetime', 315360000);
|
||||
|
||||
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||
session_start();
|
||||
}
|
||||
function generatesessionhash()
|
||||
{
|
||||
$bytes = random_bytes(36);
|
||||
return hash('sha256', bin2hex($bytes));
|
||||
}
|
||||
|
||||
function user_access($usertype)
|
||||
{
|
||||
|
||||
if (strtolower($usertype) === 'ult') {
|
||||
$accesslist = [
|
||||
'all'
|
||||
];
|
||||
} else if (strtolower($usertype) === 'super operator') {
|
||||
$accesslist = [
|
||||
'all',
|
||||
'manage_users',
|
||||
'manage_roles',
|
||||
'manage_permissions'
|
||||
];
|
||||
} else if (strtolower($usertype) === 'operator') {
|
||||
$accesslist = [
|
||||
'view_tickets',
|
||||
'create_tickets',
|
||||
'edit_tickets',
|
||||
'close_tickets',
|
||||
'manage_assigned_tickets'
|
||||
];
|
||||
} else if (strtolower($usertype) === 'coordinator') {
|
||||
$accesslist = [
|
||||
'view_tickets',
|
||||
'create_tickets',
|
||||
'edit_tickets',
|
||||
'close_tickets',
|
||||
'manage_assigned_tickets',
|
||||
'view_reports'
|
||||
];
|
||||
} else if (strtolower($usertype) === 'usher') {
|
||||
$accesslist = [
|
||||
'view_tickets',
|
||||
'create_tickets',
|
||||
'edit_tickets',
|
||||
'close_tickets'
|
||||
];
|
||||
} else if (strtolower($usertype) === 'user') {
|
||||
$accesslist = [
|
||||
'view_tickets',
|
||||
'create_tickets'
|
||||
];
|
||||
} else if (strtolower($usertype) === 'viewer') {
|
||||
$accesslist = [
|
||||
'view_tickets'
|
||||
];
|
||||
} else if (strtolower($usertype) === 'disabler') {
|
||||
$accesslist = [];
|
||||
} else {
|
||||
$accesslist = [];
|
||||
}
|
||||
|
||||
return $accesslist;
|
||||
|
||||
}
|
||||
|
||||
class WhatUserType
|
||||
{
|
||||
public $Usertype;
|
||||
|
||||
public function __construct($usertype = '---currentuser---')
|
||||
{
|
||||
if ($usertype === '---currentuser---') {
|
||||
global $CurrentUserType;
|
||||
$this->Usertype = $CurrentUserType;
|
||||
} else {
|
||||
$this->Usertype = $usertype;
|
||||
}
|
||||
}
|
||||
|
||||
private function ReadAndMatchType($arrayorStringUserTypes, $Targettype)
|
||||
{
|
||||
if (!$arrayorStringUserTypes || empty($arrayorStringUserTypes)) {
|
||||
return false;
|
||||
}
|
||||
$types = $arrayorStringUserTypes;
|
||||
if (!is_array($arrayorStringUserTypes)) {
|
||||
$types = tryjsondecode($arrayorStringUserTypes);
|
||||
}
|
||||
|
||||
if (is_array($types)) {
|
||||
return in_array($Targettype, $types);
|
||||
} else {
|
||||
return $Targettype === $types;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function IsUltimate()
|
||||
{
|
||||
return $this->ReadAndMatchType($this->Usertype, 'ult');
|
||||
}
|
||||
public function IsSuperOperator()
|
||||
{
|
||||
return $this->ReadAndMatchType($this->Usertype, 'super operator');
|
||||
}
|
||||
public function IsOperator()
|
||||
{
|
||||
return $this->ReadAndMatchType( $this->Usertype, 'operator');
|
||||
}
|
||||
public function IsCoordinator()
|
||||
{
|
||||
return $this->ReadAndMatchType( $this->Usertype, 'coordinator');
|
||||
}
|
||||
public function IsDisabler()
|
||||
{
|
||||
return $this->ReadAndMatchType( $this->Usertype, 'disabler');
|
||||
}
|
||||
public function IsAgent()
|
||||
{
|
||||
return $this->ReadAndMatchType( $this->Usertype, 'agent');
|
||||
}
|
||||
public function IsViewer()
|
||||
{
|
||||
return $this->ReadAndMatchType( $this->Usertype, 'viewer');
|
||||
}
|
||||
public function IsStoreManager()
|
||||
{
|
||||
return $this->ReadAndMatchType( $this->Usertype, 'store manager');
|
||||
}
|
||||
public function IsStoreOwner()
|
||||
{
|
||||
return $this->ReadAndMatchType( $this->Usertype, 'store owner');
|
||||
}
|
||||
public function IsRider()
|
||||
{
|
||||
return $this->ReadAndMatchType( $this->Usertype, 'rider');
|
||||
}
|
||||
public function IsAdminStaff()
|
||||
{
|
||||
return $this->ReadAndMatchType( $this->Usertype, 'admin staff');
|
||||
}
|
||||
public function IsTeamLeader()
|
||||
{
|
||||
return $this->ReadAndMatchType( $this->Usertype, 'team leader');
|
||||
}
|
||||
public function IsAudit()
|
||||
{
|
||||
return $this->ReadAndMatchType( $this->Usertype, 'audit');
|
||||
}
|
||||
public function IsRegionalDirector()
|
||||
{
|
||||
return $this->ReadAndMatchType( $this->Usertype, 'regional director');
|
||||
}
|
||||
public function IsRegularUser()
|
||||
{
|
||||
return $this->ReadAndMatchType( $this->Usertype, 'user');
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
function WhatUserType($usertype = '---currentuser---')
|
||||
{
|
||||
return new WhatUserType($usertype);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function SendCookieSession($sessionId, $expiration_strtotime = '')
|
||||
{
|
||||
if (!$sessionId) {
|
||||
return false;
|
||||
}
|
||||
if ($expiration_strtotime) {
|
||||
$expiration_strtotime = time() + 720000000;
|
||||
}
|
||||
setcookie('TA_SESSION_COOKIE', $sessionId, [
|
||||
'expires' => $expiration_strtotime, // 30 days
|
||||
'path' => '/',
|
||||
'secure' => true, // Only send over HTTPS
|
||||
'httponly' => false, // Accessible only by the server
|
||||
'samesite' => 'Strict' // CSRF protection
|
||||
]);
|
||||
}
|
||||
|
||||
function getSessionKeyEitherCookieorSession()
|
||||
{
|
||||
if (!isset($_SESSION['TA']['SESSIONKEY']) or !$_SESSION['TA']['SESSIONKEY']) {
|
||||
} else {
|
||||
return $_SESSION['TA']['SESSIONKEY'];
|
||||
}
|
||||
if (isset($_COOKIE["TA_SESSION_COOKIE"]) and $_COOKIE["TA_SESSION_COOKIE"]) {
|
||||
return $_COOKIE["TA_SESSION_COOKIE"];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function DeleteRemoveSessionKeyCookie()
|
||||
{
|
||||
setcookie("TA_SESSION_COOKIE", "", time() - 3600, "/");
|
||||
$_SESSION['TA']['SESSIONKEY'] = '';
|
||||
}
|
||||
|
||||
function UpdateSessionorCookieKey($sessionid, $expiration_strtotime = '')
|
||||
{
|
||||
if (!$sessionid) {
|
||||
return false;
|
||||
}
|
||||
$_SESSION['TA']['SESSIONKEY'] = $sessionid;
|
||||
SendCookieSession($sessionid, $expiration_strtotime);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function loginstatus()
|
||||
{
|
||||
|
||||
if (!getSessionKeyEitherCookieorSession()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$sessiondata = getActiveSessionData(getSessionKeyEitherCookieorSession());
|
||||
if (!$sessiondata) {
|
||||
return false;
|
||||
}
|
||||
$expiry = strtotime($sessiondata['expiry']);
|
||||
$now = strtotime('now');
|
||||
$active = $sessiondata['userdata']['active'];
|
||||
$expired = $now > $expiry;
|
||||
$time_difference = $expiry - $now;
|
||||
$NOT_EXPIRED = !$expired;
|
||||
|
||||
if ($sessiondata and $NOT_EXPIRED and $active) {
|
||||
if ($time_difference < 300) {
|
||||
$newexpiry = $expiry + (5 * 60);
|
||||
$newexpiry = date("Y-m-d H:i:s", $newexpiry);
|
||||
ModifySession($sessiondata['hashkey'], ['expiry' => $newexpiry]);
|
||||
$sessiondata['expiry'] = $newexpiry;
|
||||
}
|
||||
if (YesNoRandom() and false) {//remove false to allow regeneration of id. currently logsuser out immediately
|
||||
$newsessionid = regeneratesessionidANDUpdateSessionVar();
|
||||
if ($newsessionid) {
|
||||
$sessiondata['hashkey'] = $newsessionid;
|
||||
}
|
||||
}
|
||||
UpdateSessionorCookieKey($sessiondata['hashkey'], strtotime($sessiondata['expiry']));
|
||||
return $sessiondata;
|
||||
} else {
|
||||
deleteSession(getSessionKeyEitherCookieorSession());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function getRandomNumber($length = 1)
|
||||
{
|
||||
$random_bytes = openssl_random_pseudo_bytes($length);
|
||||
return ord($random_bytes[0]);
|
||||
}
|
||||
function YesNoRandom()
|
||||
{
|
||||
$regenerate_threshold = 50;
|
||||
$random_number = getRandomNumber(1);
|
||||
if ($random_number <= $regenerate_threshold) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function regeneratesessionid($currensessionhash)
|
||||
{
|
||||
if (!$currensessionhash) {
|
||||
return false;
|
||||
}
|
||||
$newhash = generatesessionhash();
|
||||
$data['hashkey'] = $newhash;
|
||||
$modify = ModifySession($currensessionhash, $data);
|
||||
if (!$modify) {
|
||||
return false;
|
||||
}
|
||||
return $newhash;
|
||||
}
|
||||
|
||||
function regeneratesessionidANDUpdateSessionVar()
|
||||
{
|
||||
$sessionnewid = regeneratesessionid(getSessionKeyEitherCookieorSession());
|
||||
if ($sessionnewid) {
|
||||
UpdateSessionorCookieKey($sessionnewid);
|
||||
return $sessionnewid;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function tryloginwcookies($SESSION_ID)
|
||||
{
|
||||
$sessiondata = getActiveSessionData($SESSION_ID);
|
||||
if (!$sessiondata) {
|
||||
return false;
|
||||
}
|
||||
//$_SESSION['TA']['SESSIONKEY']
|
||||
|
||||
}
|
||||
function getUserType()
|
||||
{
|
||||
$loginstatus = loginstatus();
|
||||
if (!$loginstatus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (isset($loginstatus['userdata']['acct_type']) and $loginstatus['userdata']['acct_type']) {
|
||||
return $loginstatus['userdata']['acct_type'];
|
||||
}
|
||||
}
|
||||
|
||||
function IsUserCoordinator()
|
||||
{
|
||||
if (strtolower(getUserType()) == 'coordinator') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function IsUserUltimate()
|
||||
{
|
||||
if (strtolower(getUserType()) == 'ult') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function IsUserOperator()
|
||||
{
|
||||
if (strtolower(getUserType()) == 'operator') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function IsUserSuperOperator()
|
||||
{
|
||||
if (strtolower(getUserType()) == 'super operator') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function IsUserUsher()
|
||||
{
|
||||
if (strtolower(getUserType()) == 'usher') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function IsUserViewer()
|
||||
{
|
||||
if (strtolower(getUserType()) == 'viewer') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function IsUserDisabler()
|
||||
{
|
||||
if (strtolower(getUserType()) == 'disabler') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function IsNormalUser()
|
||||
{
|
||||
if (strtolower(getUserType()) == 'user') {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function CurrentUserUID()
|
||||
{
|
||||
$loginstatus = loginstatus()['userdata']['uid'] ?? false;
|
||||
return $loginstatus;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function loginnow($username, $password, $keep_alive = false)
|
||||
{
|
||||
if (!$username or !$password) {
|
||||
|
||||
return false;
|
||||
}
|
||||
if (loginstatus()) {
|
||||
return loginstatus();
|
||||
}
|
||||
$trylogin = trylogin($username, $password);
|
||||
if (!$trylogin) {
|
||||
|
||||
return false;
|
||||
}
|
||||
$userid = $trylogin['uid'];
|
||||
if ($keep_alive) {
|
||||
$expiry = date("Y-m-d H:i:s", strtotime('+ 10 years'));
|
||||
} else {
|
||||
$expiry = date("Y-m-d H:i:s", strtotime('+ 2 hours'));
|
||||
}
|
||||
$newsessionkey = NewSession($userid, 1, $expiry);
|
||||
|
||||
$_SESSION['TA']['SESSIONKEY'] = $newsessionkey;
|
||||
|
||||
return $newsessionkey;
|
||||
}
|
||||
|
||||
function logoutnow()
|
||||
{
|
||||
deleteAllUserSessions(CurrentUserUID());
|
||||
unset($_SESSION['TA']);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function NewSession($userid, $active = 1, $expiry = false)
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
$user = checkifexists('users', ['uid' => $userid], ['hashkey', 'nickname', 'acct_type', 'mnumber', 'active', 'parentuid', 'targetuids', 'multiple_logins']);
|
||||
if (!$user) {
|
||||
return 'NO USER';
|
||||
}
|
||||
|
||||
if (checkifexists('sessions', ['userid' => $userid, 'active' => 1], $fieldstoselectarray = '')) {
|
||||
if (!$user['multiple_logins']) {
|
||||
deleteAllUserSessions($userid);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$expiry) {
|
||||
$expiry = date("Y-m-d H:i:s", strtotime('+10 years'));
|
||||
}
|
||||
unset($user['password']);
|
||||
unset($user['creation_date']);
|
||||
unset($user['modified_date']);
|
||||
$newhash = generatesessionhash();
|
||||
$data['hashkey'] = $newhash;
|
||||
$data['userid'] = $userid;
|
||||
$data['active'] = $active;
|
||||
$data['expiry'] = $expiry;
|
||||
$data['userdata'] = json_encode($user);
|
||||
|
||||
$usertype = user_access($user['acct_type']);
|
||||
|
||||
$data['accesslist'] = json_encode($usertype);
|
||||
$key = insertintodb(DB(), 'sessions', $data);
|
||||
|
||||
$sessiondata = getSessionData($newhash);
|
||||
if (!$sessiondata) {
|
||||
return false;
|
||||
}
|
||||
$sessionHistory = NewSession_History($userid, $sessiondata, $active, $expiry);
|
||||
|
||||
if (!$sessionHistory) {
|
||||
deleteSession($newhash);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $newhash;
|
||||
}
|
||||
|
||||
function getSessionData($sessionhash)
|
||||
{
|
||||
$sessionhash = checkifexists('sessions', ['hashkey' => $sessionhash]);
|
||||
if ($sessionhash and is_array($sessionhash) and !empty($sessionhash)) {
|
||||
$sessiondata['userdata'] = json_decode($sessionhash['userdata'], 1);
|
||||
$userhashkey = $sessiondata['userdata']['hashkey'];
|
||||
$fieldstoselect = [
|
||||
'uid',
|
||||
'hashkey',
|
||||
'nickname',
|
||||
'acct_type',
|
||||
'mnumber',
|
||||
'active',
|
||||
'parentuid',
|
||||
'targetuids'
|
||||
];
|
||||
$sessionhash['userdata'] = GetUserDatabyUID($userhashkey, $fieldstoselect);
|
||||
$usertype = $sessionhash['userdata']['acct_type'];
|
||||
$sessionhash['accesslist'] = user_access($usertype);
|
||||
return $sessionhash;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getActiveSessionData($sessionhash)
|
||||
{
|
||||
$session = getSessionData($sessionhash);
|
||||
if ($session and $session['active'] === 1) {
|
||||
return $session;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
function deleteSession($sessionhash)
|
||||
{
|
||||
deletefromdb('sessions', ['hashkey' => $sessionhash]);
|
||||
if (checkifexists('sessions', ['hashkey' => $sessionhash], ['hashkey'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
function deleteAllUserSessions($userid)
|
||||
{
|
||||
deletefromdb('sessions', ['userid' => $userid]);
|
||||
if (checkifexists('sessions', ['userid' => $userid], ['hashkey'])) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function FindCurrentSessionForUser($userid)
|
||||
{
|
||||
$search = checkifexists('sessions', ['userid' => $userid, 'active' => 1], ['hashkey']);
|
||||
if ($search and isset($search['hashkey'])) {
|
||||
return $search['hashkey'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function ModifySession($sessionhash, $newdata)
|
||||
{
|
||||
if (!$sessionhash) {
|
||||
return false;
|
||||
}
|
||||
$check = checkifexists('sessions', ['hashkey' => $sessionhash]);
|
||||
if (!$check or empty($check['hashkey'] or !$check['hashkey'])) {
|
||||
return false;
|
||||
}
|
||||
$whereArray = ['hashkey' => $sessionhash];
|
||||
return updatedbsimple(DB(), 'sessions', $newdata, $whereArray);
|
||||
}
|
||||
|
||||
function ExtendSession($sessionhash, $newexpiry = false)
|
||||
{
|
||||
if (!$newexpiry) {
|
||||
$newexpiry = date("Y-m-d H:i:s", strtotime('+3 days'));
|
||||
}
|
||||
return ModifySession($sessionhash, ['expiry' => $newexpiry]);
|
||||
}
|
||||
function ExtendSessionbyUID($UID, $newexpiry = false)
|
||||
{
|
||||
if (!$newexpiry) {
|
||||
$newexpiry = date("Y-m-d H:i:s", strtotime('+3 days'));
|
||||
}
|
||||
$usersession = FindCurrentSessionForUser($UID);
|
||||
return ModifySession($usersession, ['expiry' => $newexpiry]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//SessionHistory
|
||||
|
||||
|
||||
function NewSession_History($userid, $sessiondata, $active = 1, $expiry = false)
|
||||
{
|
||||
|
||||
|
||||
|
||||
$newhash = generatesessionhash();
|
||||
$data['hashkey'] = $newhash;
|
||||
$data['userid'] = $sessiondata['userid'];
|
||||
$data['old_hashkey'] = $sessiondata['hashkey'];
|
||||
$data['active'] = $sessiondata['active'];
|
||||
$data['expiry'] = $sessiondata['expiry'];
|
||||
$data['accesslist'] = $sessiondata['accesslist'];
|
||||
$data['userdata'] = $sessiondata['userdata'];
|
||||
$data['login_time'] = serverdatetimesql();
|
||||
$data['serverdata'] = json_encode($_SERVER);
|
||||
$data['ip_address'] = $_SERVER['REMOTE_ADDR'];
|
||||
|
||||
|
||||
$key = insertintodb(DB(), 'session_history', $data);
|
||||
|
||||
if (!$key) {
|
||||
return false;
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
|
||||
function NewLog($log_type, $log_category, $description, $useruid = '')
|
||||
{
|
||||
|
||||
if (!$useruid) {
|
||||
$useruid = CurrentUserUID();
|
||||
}
|
||||
|
||||
$data['log_time'] = serverdatetimesql();
|
||||
$data['log_type'] = $log_type;
|
||||
$data['log_category'] = $log_category;
|
||||
$data['description'] = $description;
|
||||
$data['server_data'] = json_encode($_SERVER);
|
||||
$data['session_data'] = json_encode($_SESSION);
|
||||
$data['useruid'] = $useruid;
|
||||
|
||||
|
||||
$key = insertintodb(DB(), 'logs', $data);
|
||||
|
||||
|
||||
if (!$key) {
|
||||
return false;
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
|
||||
|
||||
?>
|
||||
945
app/Http/Controllers/Helpers/Legacy/LibLegacy.php
Normal file
945
app/Http/Controllers/Helpers/Legacy/LibLegacy.php
Normal file
@@ -0,0 +1,945 @@
|
||||
<?php
|
||||
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Helpers\Permissions;
|
||||
|
||||
use App\Enums\UserTypes;
|
||||
use Hypervel\Http\Request;
|
||||
use App\Models\User;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use App\Enums\UserActions;
|
||||
use App\Http\Controllers\Helpers\QueryHelper;
|
||||
use App\Models\Market\Product;
|
||||
use App\Models\Market\Store;
|
||||
use Exception;
|
||||
|
||||
class LibLegacy
|
||||
{
|
||||
|
||||
function endsWithSlash($path)
|
||||
{
|
||||
if (strlen($path) === 0) {
|
||||
return false;
|
||||
}
|
||||
$lastChar = substr($path, -1);
|
||||
return $lastChar === '/' || $lastChar === '\\';
|
||||
}
|
||||
|
||||
function ArraytoHash($array)
|
||||
{
|
||||
if (!is_array($array) || empty($array)) {
|
||||
return false;
|
||||
}
|
||||
$jsonString = json_encode($array);
|
||||
$hash = hash('sha256', $jsonString);
|
||||
return $hash;
|
||||
}
|
||||
|
||||
function SetNoCache()
|
||||
{
|
||||
header('Cache-Control: no-cache');
|
||||
}
|
||||
|
||||
function SetCacheTime($seconds)
|
||||
{
|
||||
if ($seconds) {
|
||||
return false;
|
||||
}
|
||||
header('Cache-Control: max-age=' . $seconds . '');
|
||||
}
|
||||
function SetCacheTimeMinutes($minutes)
|
||||
{
|
||||
if ($minutes) {
|
||||
return false;
|
||||
}
|
||||
$seconds = $minutes * 60;
|
||||
header('Cache-Control: max-age=' . $seconds . '');
|
||||
}
|
||||
|
||||
function SetCache1Year()
|
||||
{
|
||||
header('Cache-Control: max-age=31536000, public');
|
||||
}
|
||||
|
||||
function json_array_echo($array)
|
||||
{
|
||||
jsonheader();
|
||||
echo json_encode($array);
|
||||
}
|
||||
function tryjsondecode($string, $arrayoutput = true)
|
||||
{
|
||||
if (is_array($string)) {
|
||||
return $string;
|
||||
}
|
||||
if (!$string) {
|
||||
return $string;
|
||||
}
|
||||
$json = json_decode($string, $arrayoutput);
|
||||
if ($json === null) {
|
||||
return $string;
|
||||
} else {
|
||||
return $json;
|
||||
}
|
||||
}
|
||||
|
||||
function tryjsonencode($array)
|
||||
{
|
||||
if (!$array) {
|
||||
return json_encode([]);
|
||||
}
|
||||
if (is_array($array)) {
|
||||
$result = json_encode($array);
|
||||
} else {
|
||||
$result = json_encode([$array]);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function filterArrayColumns($array, $columns)
|
||||
{
|
||||
return array_map(function ($item) use ($columns) {
|
||||
return array_intersect_key($item, array_flip($columns));
|
||||
}, $array);
|
||||
}
|
||||
|
||||
function logmaker($filename, $nolog = false)
|
||||
{
|
||||
$main = new class ($filename, $nolog) {
|
||||
public $filename;
|
||||
public $loglistarray = [];
|
||||
|
||||
public function __construct($filename, $nolog)
|
||||
{
|
||||
$this->filename = $filename;
|
||||
if ($nolog) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function add($str)
|
||||
{
|
||||
$this->loglistarray[] = $str;
|
||||
}
|
||||
|
||||
public function done()
|
||||
{
|
||||
file_put_contents($this->filename, implode("\r\n\r\n", $this->loglistarray));
|
||||
}
|
||||
|
||||
function __destruct()
|
||||
{
|
||||
file_put_contents($this->filename, implode("\r\n\r\n", $this->loglistarray));
|
||||
}
|
||||
};
|
||||
return $main;
|
||||
}
|
||||
|
||||
function createThumbnail($sourcePath, $destinationPath = null, $thumbWidth = 64, $thumbHeight = 64)
|
||||
{
|
||||
list($width, $height, $type) = getimagesize($sourcePath);
|
||||
$srcImage = null;
|
||||
|
||||
switch ($type) {
|
||||
case IMAGETYPE_JPEG:
|
||||
$srcImage = imagecreatefromjpeg($sourcePath);
|
||||
break;
|
||||
case IMAGETYPE_PNG:
|
||||
$srcImage = imagecreatefrompng($sourcePath);
|
||||
break;
|
||||
case IMAGETYPE_GIF:
|
||||
$srcImage = imagecreatefromgif($sourcePath);
|
||||
break;
|
||||
default:
|
||||
throw new Exception('Unsupported image type');
|
||||
}
|
||||
|
||||
$thumbImage = imagecreatetruecolor($thumbWidth, $thumbHeight);
|
||||
|
||||
imagecopyresampled($thumbImage, $srcImage, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $width, $height);
|
||||
|
||||
if (!$destinationPath) {
|
||||
$basepath = dirname($sourcePath);
|
||||
$basenameWithoutExtension = pathinfo($sourcePath, PATHINFO_FILENAME);
|
||||
$extension = pathinfo($sourcePath, PATHINFO_EXTENSION) ?? '';
|
||||
if ($extension) {
|
||||
$extension = '.' + $extension;
|
||||
}
|
||||
$newfilename = $basenameWithoutExtension + '_thumbnail' + $extension;
|
||||
$destinationPath = $basepath + '/' + $newfilename;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case IMAGETYPE_JPEG:
|
||||
imagejpeg($thumbImage, $destinationPath);
|
||||
break;
|
||||
case IMAGETYPE_PNG:
|
||||
imagepng($thumbImage, $destinationPath);
|
||||
break;
|
||||
case IMAGETYPE_GIF:
|
||||
imagegif($thumbImage, $destinationPath);
|
||||
break;
|
||||
}
|
||||
|
||||
imagedestroy($srcImage);
|
||||
imagedestroy($thumbImage);
|
||||
}
|
||||
|
||||
|
||||
function trim_ending_hyphen($string)
|
||||
{
|
||||
if (substr($string, -1) === "-") {
|
||||
return substr($string, 0, -1); // Remove the last character (hyphen)
|
||||
} else {
|
||||
return $string; // Return the original string if it doesn't end with a hyphen
|
||||
}
|
||||
}
|
||||
|
||||
function convertStringToSQLDateTime($stringDate)
|
||||
{
|
||||
$timestamp = strtotime($stringDate);
|
||||
$sqlDatetime = date("Y-m-d H:i:s", $timestamp);
|
||||
return $sqlDatetime;
|
||||
}
|
||||
|
||||
|
||||
function comparestringnumberstoarray($string, $comparearray, $inorder = false)
|
||||
{
|
||||
$mainarray = explode('-', $string);
|
||||
$mainarray = array_values($mainarray);
|
||||
|
||||
$lastvaluemainarray = $mainarray[count($mainarray) - 1];
|
||||
if ($lastvaluemainarray === '') {
|
||||
array_pop($mainarray);
|
||||
}
|
||||
|
||||
if (in_array('', $mainarray)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$inorder) {
|
||||
sort($mainarray);
|
||||
sort($comparearray);
|
||||
}
|
||||
|
||||
return $mainarray == $comparearray;
|
||||
}
|
||||
|
||||
|
||||
function refreshpage()
|
||||
{
|
||||
return '<script>location.reload()</script>';
|
||||
}
|
||||
function refreshpagenopost()
|
||||
{
|
||||
|
||||
return '<script>window.location = window.location.href</script>';
|
||||
}
|
||||
function refresh()
|
||||
{
|
||||
header('Location: .');
|
||||
}
|
||||
function jschangetopurl($url)
|
||||
{
|
||||
|
||||
return '<script>top.location = "' . $url . '";</script>';
|
||||
}
|
||||
|
||||
function jschangetopurlnoscripttag($url)
|
||||
{
|
||||
|
||||
return "top.location = '" . $url . "';";
|
||||
}
|
||||
function jsopenmodal($modalname)
|
||||
{
|
||||
return '<script>
|
||||
$(document).ready(function(){
|
||||
$("#' . $modalname . '").modal();
|
||||
});
|
||||
</script>';
|
||||
}
|
||||
|
||||
function jsonheader()
|
||||
{
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
}
|
||||
|
||||
function simpleredirect($requesturl, $functiontoexecute, $reqtype = '', $fallbackfunction = NULL, $conditiontrue = NULL, $caching = false)
|
||||
{
|
||||
|
||||
// sample url = http://website.com/?u=name/{john}
|
||||
// sample function syntax simpleredirect('name/{}')
|
||||
|
||||
if (file_exists('sim.txt')) {
|
||||
unlink('sim.txt');
|
||||
}
|
||||
$addlog = function ($str) {
|
||||
@file_put_contents('sim.txt', $str . "\r\n", FILE_APPEND);
|
||||
};
|
||||
|
||||
|
||||
|
||||
$urlcheck = geturlparameters($requesturl);
|
||||
|
||||
$addlog('chekingurl=' . getappendedurl() . ' requrl=' . $requesturl);
|
||||
|
||||
if ($urlcheck === FALSE) {
|
||||
$addlog('urlcheck=false');
|
||||
return false;
|
||||
}
|
||||
$addlog('chekingurl success');
|
||||
if ($reqtype == '') {
|
||||
$reqtype = 'get';
|
||||
$addlog('reqtype unknown changing to get');
|
||||
} else {
|
||||
$reqtype = strtolower($reqtype);
|
||||
|
||||
$addlog('reqtype = ' . $reqtype);
|
||||
}
|
||||
|
||||
if ($reqtype !== 'post' and $reqtype !== 'get') {
|
||||
$reqtype = 'get';
|
||||
$addlog('reqtype not post or get changing to get');
|
||||
}
|
||||
|
||||
if (strtolower($reqtype) !== strtolower($_SERVER['REQUEST_METHOD'])) {
|
||||
$addlog('reqtype not the same reqtype = ' . $reqtype . ' requestmethod = ' . $_SERVER['REQUEST_METHOD']);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$GLOBALS['urlparameters'] = $urlcheck;
|
||||
|
||||
if ($conditiontrue !== NULL and !$conditiontrue) {
|
||||
$addlog('condition not true exiting');
|
||||
if ($fallbackfunction) {
|
||||
$fallbackfunction();
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!$caching) {
|
||||
$addlog('removing cache');
|
||||
removecaching();
|
||||
} elseif ($caching) {
|
||||
$addlog('adding cache');
|
||||
if ($caching === true) {
|
||||
SetCache1Year();
|
||||
} elseif (is_numeric($caching)) {
|
||||
SetCacheTimeMinutes($caching);
|
||||
} else {
|
||||
SetCache1Year();
|
||||
}
|
||||
}
|
||||
$addlog('executing function');
|
||||
$functiontoexecute($urlcheck);
|
||||
}
|
||||
|
||||
|
||||
function simpleredirectfile($requesturl, $file, $reqtype = '', $fallbackfunction = NULL, $conditiontrue = NULL, $caching = FALSE)
|
||||
{
|
||||
$filefunct = function () use ($file) {
|
||||
readfile($file);
|
||||
};
|
||||
simpleredirect($requesturl, $filefunct, $reqtype, $fallbackfunction, $conditiontrue, $caching);
|
||||
}
|
||||
|
||||
|
||||
function checkifstringisenclosedinbrackets($string)
|
||||
{
|
||||
// checks if string is enclosed in brackers ex {name}
|
||||
//returns string inside and returns false if there is no bracket
|
||||
if (substr($string, 0, 1) === "{" && substr($string, -1) === "}") {
|
||||
$string = substr($string, 1, -1);
|
||||
} else {
|
||||
$string = false;
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function geturlparameters($urlparam)
|
||||
{
|
||||
//urlparam = 'users/'
|
||||
if ($urlparam === '') {
|
||||
$urlparam = [];
|
||||
} else {
|
||||
$urlparam = explode('/', $urlparam);
|
||||
}
|
||||
$url = geturlarray();
|
||||
|
||||
|
||||
if (count($url) !== count($urlparam)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$params = [];
|
||||
foreach ($urlparam as $key => $value) {
|
||||
if ($urlparam[$key] !== $url[$key]) {
|
||||
$enclosed = checkifstringisenclosedinbrackets($value);
|
||||
if ($enclosed) {
|
||||
$params[$enclosed] = $url[$key];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
function getLastDigitSegments($string, $n)
|
||||
{
|
||||
$parts = explode('-', $string);
|
||||
$lastSegments = array_slice($parts, -$n);
|
||||
return implode('-', $lastSegments);
|
||||
}
|
||||
function geturlarray()
|
||||
{
|
||||
$url = getappendedurl();
|
||||
|
||||
if (substr($url, 0, 1) === "/") {
|
||||
$uri = substr($url, 1);
|
||||
} else {
|
||||
$uri = $url;
|
||||
}
|
||||
|
||||
|
||||
|
||||
$uri = array_values(array_filter(explode('/', $uri)));
|
||||
unset($uri[0]);
|
||||
$uri = array_values($uri);
|
||||
if (empty($uri)) {
|
||||
return [];
|
||||
}
|
||||
if (substr($uri[0], 0, 1) === "?") {
|
||||
$uri[0] = substr($uri[0], 1);
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
function getappendedurl()
|
||||
{
|
||||
//get appended url sample url https:/google.com/aslkdfj/asdf/asdf
|
||||
//result aslkdfj/asdf/asdf
|
||||
return $_SERVER['REQUEST_URI'];
|
||||
}
|
||||
|
||||
|
||||
function removecaching()
|
||||
{
|
||||
if (headers_sent()) {
|
||||
return false;
|
||||
}
|
||||
header_remove('ETag');
|
||||
header_remove('Pragma');
|
||||
header_remove('Cache-Control');
|
||||
header_remove('Last-Modified');
|
||||
header_remove('Expires');
|
||||
|
||||
// set header
|
||||
header('Expires: Thu, 1 Jan 1970 00:00:00 GMT');
|
||||
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
||||
header('Cache-Control: post-check=0, pre-check=0', false);
|
||||
header('Pragma: no-cache');
|
||||
}
|
||||
|
||||
|
||||
function is_valid_var($var)
|
||||
{
|
||||
if (!isset($var)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (empty($var) || $var === false || is_null($var)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $var;
|
||||
}
|
||||
|
||||
function ConvertDatetoMDYYYY($datestring)
|
||||
{
|
||||
//from YYYY-MM-DD to M/D/YYYY
|
||||
if (!$datestring) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$timestamp = strtotime($datestring);
|
||||
$formattedDate = date('n/j/Y', $timestamp);
|
||||
return $formattedDate;
|
||||
}
|
||||
|
||||
function PNGtoJPGNN($filelocation, $newfilelocation)
|
||||
{
|
||||
$new_pic = imagecreatefrompng($filelocation);
|
||||
$w = imagesx($new_pic);
|
||||
$h = imagesy($new_pic);
|
||||
$white = imagecreatetruecolor($w, $h);
|
||||
$bg = imagecolorallocate($white, 255, 255, 255);
|
||||
imagefill($white, 0, 0, $bg);
|
||||
|
||||
imagecopy($white, $new_pic, 0, 0, 0, 0, $w, $h);
|
||||
$success = imagejpeg($white, $newfilelocation);
|
||||
imagedestroy($new_pic);
|
||||
imagedestroy($white);
|
||||
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
|
||||
function PNGtoJPG($pngFilePath, $jpgFilePath, $maxWidth = false, $maxHeight = false, $quality = 100)
|
||||
{
|
||||
$pngImage = imagecreatefrompng($pngFilePath);
|
||||
if (!$pngImage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$originalWidth = imagesx($pngImage);
|
||||
$originalHeight = imagesy($pngImage);
|
||||
if (!$maxWidth && !$maxHeight) {
|
||||
$maxWidth = $originalWidth;
|
||||
$maxHeight = $originalHeight;
|
||||
}
|
||||
|
||||
$aspectRatio = $originalWidth / $originalHeight;
|
||||
|
||||
if ($maxWidth / $maxHeight > $aspectRatio) {
|
||||
$newWidth = $maxHeight * $aspectRatio;
|
||||
$newHeight = $maxHeight;
|
||||
} else {
|
||||
$newWidth = $maxWidth;
|
||||
$newHeight = $maxWidth / $aspectRatio;
|
||||
}
|
||||
$newWidth = ceil($newWidth);
|
||||
$newHeight = ceil(num: $newHeight);
|
||||
|
||||
$jpgImage = imagecreatetruecolor($newWidth, $newHeight);
|
||||
imagecopyresampled($jpgImage, $pngImage, 0, 0, 0, 0, $newWidth, $newHeight, $originalWidth, $originalHeight);
|
||||
$success = imagejpeg($jpgImage, $jpgFilePath, $quality);
|
||||
|
||||
imagedestroy($pngImage);
|
||||
imagedestroy($jpgImage);
|
||||
return $success;
|
||||
}
|
||||
|
||||
function PNGtoWebP($pngFilePath, $webpFilePath, $maxWidth = false, $maxHeight = false, $quality = 100)
|
||||
{
|
||||
$pngImage = imagecreatefrompng($pngFilePath);
|
||||
if (!$pngImage) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$originalWidth = imagesx($pngImage);
|
||||
$originalHeight = imagesy($pngImage);
|
||||
|
||||
if (!$maxWidth && !$maxHeight) {
|
||||
$maxWidth = $originalWidth;
|
||||
$maxHeight = $originalHeight;
|
||||
}
|
||||
|
||||
$aspectRatio = $originalWidth / $originalHeight;
|
||||
|
||||
if ($maxWidth / $maxHeight > $aspectRatio) {
|
||||
$newWidth = $maxHeight * $aspectRatio;
|
||||
$newHeight = $maxHeight;
|
||||
} else {
|
||||
$newWidth = $maxWidth;
|
||||
$newHeight = $maxWidth / $aspectRatio;
|
||||
}
|
||||
$newWidth = ceil($newWidth);
|
||||
$newHeight = ceil(num: $newHeight);
|
||||
|
||||
$webpImage = imagecreatetruecolor($newWidth, $newHeight);
|
||||
|
||||
imagealphablending($webpImage, false);
|
||||
imagesavealpha($webpImage, true);
|
||||
$transparent = imagecolorallocatealpha($webpImage, 255, 255, 255, 127);
|
||||
imagefilledrectangle($webpImage, 0, 0, $newWidth, $newHeight, $transparent);
|
||||
|
||||
imagecopyresampled($webpImage, $pngImage, 0, 0, 0, 0, $newWidth, $newHeight, $originalWidth, $originalHeight);
|
||||
|
||||
$success = imagewebp($webpImage, $webpFilePath, $quality);
|
||||
imagedestroy($pngImage);
|
||||
imagedestroy($webpImage);
|
||||
return $success;
|
||||
}
|
||||
|
||||
function BatchConvertPNG($oldfolder, $newfolder, $newtype = 'webp', $maxWidth = false, $maxHeight = false, $quality = 100)
|
||||
{
|
||||
//filename from old folder to new folder is copied exactly even the extensions are the same.
|
||||
if (!$newtype) {
|
||||
$newtype = 'webp';
|
||||
}
|
||||
$oldfolder = AddSlashtoStrifNolastSlash($oldfolder);
|
||||
$newfolder = AddSlashtoStrifNolastSlash($newfolder);
|
||||
$processIMG = function ($oldfilename) use ($newfolder, $newtype, $maxWidth, $maxHeight, $quality) {
|
||||
if (!file_exists($oldfilename)) {
|
||||
echo $oldfilename . '<br>';
|
||||
return false;
|
||||
}
|
||||
$mimetype = mime_content_type($oldfilename);
|
||||
if (!str_contains($mimetype, 'png')) {
|
||||
return false;
|
||||
}
|
||||
$basefilename = basename($oldfilename);
|
||||
$newjpglocation = $newfolder . $basefilename;
|
||||
|
||||
if (strtolower($newtype) === 'webp') {
|
||||
return PNGtoWebP($oldfilename, $newjpglocation, $maxWidth, $maxHeight, $quality);
|
||||
} elseif (strtolower($newtype) === 'jpg' || strtolower($newtype) === 'jpeg') {
|
||||
return PNGtoJPG($oldfilename, $newjpglocation, $maxWidth, $maxHeight, $quality);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
$dir = scandir($oldfolder);
|
||||
foreach ($dir as $keyy => $file) {
|
||||
if ($file === '..') {
|
||||
unset($dir[$keyy]);
|
||||
continue;
|
||||
}
|
||||
if ($file === '.') {
|
||||
unset($dir[$keyy]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
foreach ($dir as $file) {
|
||||
$oldfilename = $oldfolder . '/' . $file;
|
||||
$processIMG($oldfilename);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function AddSlashtoStrifNolastSlash($str)
|
||||
{
|
||||
if (!$str) {
|
||||
return false;
|
||||
}
|
||||
$lastchar = substr($str, -1);
|
||||
if ($lastchar !== '/' && $lastchar !== '\\') {
|
||||
$str .= '/';
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
|
||||
function formatDateTimetoReadable($dateTimeString)
|
||||
{
|
||||
if (empty($dateTimeString)) {
|
||||
return '';
|
||||
}
|
||||
$date = new DateTime($dateTimeString);
|
||||
if (!$date) {
|
||||
return '';
|
||||
}
|
||||
$formattedDate = $date->format('F j, Y');
|
||||
$hours = (int) $date->format('g');
|
||||
$minutes = (int) $date->format('i');
|
||||
$ampm = $date->format('A');
|
||||
$formattedMinutes = str_pad($minutes, 2, '0', STR_PAD_LEFT);
|
||||
return strpos($dateTimeString, ' ') !== false
|
||||
? "{$formattedDate} {$hours}:{$formattedMinutes}{$ampm}"
|
||||
: "{$formattedDate}";
|
||||
}
|
||||
|
||||
function generateDatesOLD($daysArray, $months = 1, $sort = true)
|
||||
{
|
||||
if (!$daysArray) {
|
||||
return false;
|
||||
}
|
||||
if (!is_array($daysArray)) {
|
||||
$daysArray = tryjsondecode($daysArray);
|
||||
}
|
||||
if (!is_array($daysArray)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dates = [];
|
||||
|
||||
// Iterate through each day in the array
|
||||
foreach ($daysArray as $day) {
|
||||
list($weekday, $time) = $day;
|
||||
|
||||
$currentDate = new DateTime();
|
||||
$currentDate->modify("next $weekday");
|
||||
|
||||
for ($i = 0; $i < $months; $i++) {
|
||||
// Add the time to the current date
|
||||
$dateWithTime = $currentDate->format('Y-m-d') . ' ' . $time;
|
||||
|
||||
// Add the date to the array
|
||||
$dates[] = $dateWithTime;
|
||||
|
||||
// Move to the next week (incrementing only the week part)
|
||||
$currentDate->modify('+1 week');
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the dates if specified
|
||||
if ($sort) {
|
||||
sort($dates);
|
||||
}
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function generateDatesNEW($daysArray, $months = 2, $sort = true)
|
||||
{
|
||||
if (!$daysArray) {
|
||||
return false;
|
||||
}
|
||||
if (!is_array($daysArray)) {
|
||||
$daysArray = tryjsondecode($daysArray);
|
||||
}
|
||||
if (!is_array($daysArray)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dates = [];
|
||||
|
||||
// Iterate through each day in the array
|
||||
foreach ($daysArray as $day) {
|
||||
list($weekday, $time) = $day;
|
||||
|
||||
$currentDate = new DateTime();
|
||||
|
||||
// If today is the specified weekday, include it
|
||||
if (strtolower($currentDate->format('D')) === strtolower($weekday)) {
|
||||
$dateWithTime = $currentDate->format('Y-m-d') . ' ' . $time;
|
||||
$dates[] = $dateWithTime;
|
||||
}
|
||||
|
||||
// Move to the next week (incrementing only the week part)
|
||||
$currentDate->modify("next $weekday");
|
||||
|
||||
for ($i = 1; $i < $months; $i++) {
|
||||
// Add the time to the current date
|
||||
$dateWithTime = $currentDate->format('Y-m-d') . ' ' . $time;
|
||||
|
||||
// Add the date to the array
|
||||
$dates[] = $dateWithTime;
|
||||
|
||||
// Move to the next week (incrementing only the week part)
|
||||
$currentDate->modify('+1 week');
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the dates if specified
|
||||
if ($sort) {
|
||||
sort($dates);
|
||||
}
|
||||
|
||||
return $dates;
|
||||
}
|
||||
|
||||
function generateDates($daysArray, $days = 8, $sort = true)
|
||||
{
|
||||
$dates = generateDatesNEW($daysArray, $months = 4, $sort);
|
||||
unset($dates[0]);
|
||||
return array_splice($dates, 0, $days, []);
|
||||
}
|
||||
|
||||
function GetTotalAmountfromArray($array)
|
||||
{
|
||||
if (!is_array($array)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$total = 0;
|
||||
|
||||
foreach ($array as $amount) {
|
||||
if (is_numeric($amount) && $amount !== '') {
|
||||
$total += $amount;
|
||||
}
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
function removecolumnsfrom2Darray($array, $arraycolumnstoremove)
|
||||
{
|
||||
if (!is_array($array) or !is_array($arraycolumnstoremove)) {
|
||||
return false;
|
||||
}
|
||||
foreach ($array as $key => $value) {
|
||||
foreach ($value as $skey => $sval) {
|
||||
if (in_array($skey, $arraycolumnstoremove)) {
|
||||
unset($array[$key][$skey]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sqlarray_2dfilter($array, $columnname, string $stringtosearch, $exact = FALSE, $caseinsensitive = false, $simplesearchwith_strpos = false)
|
||||
{
|
||||
if ($columnname === '' or $columnname === null or $columnname === false) {
|
||||
return FALSE;
|
||||
}
|
||||
if (!$array) {
|
||||
return false;
|
||||
}
|
||||
$datatosearch = array_column($array, $columnname);
|
||||
if ($caseinsensitive) {
|
||||
$stringtosearch = strtolower($stringtosearch);
|
||||
foreach ($datatosearch as $key => $value) {
|
||||
$datatosearch[$key] = strtolower($value);
|
||||
}
|
||||
}
|
||||
|
||||
if ($exact) {
|
||||
$keylist = array_keys($datatosearch, $stringtosearch);
|
||||
} else {
|
||||
if ($simplesearchwith_strpos) {
|
||||
$keylist = [];
|
||||
foreach ($datatosearch as $sskey => $ssvalue) {
|
||||
if ($caseinsensitive) {
|
||||
if (stripos($ssvalue, $stringtosearch)) {
|
||||
$keylist[] = $sskey;
|
||||
}
|
||||
} elseif (!$caseinsensitive) {
|
||||
if (strpos($ssvalue, $stringtosearch)) {
|
||||
$keylist[] = $sskey;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$stringtosearch = preg_quote($stringtosearch, '/'); // remove if an error is encountered
|
||||
$keylist = array_keys(preg_grep('/(?i)' . $stringtosearch . '/', $datatosearch));
|
||||
}
|
||||
}
|
||||
$res = [];
|
||||
foreach ($keylist as $value) {
|
||||
$res[] = $array[$value];
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
function array2d_removeduplicate($array, $columnname, $reorder = FALSE)
|
||||
{
|
||||
$keylist = array_keys(array_unique(array_column($array, $columnname)));
|
||||
$newarray = [];
|
||||
foreach ($keylist as $value) {
|
||||
$newarray[$value] = $array[$value];
|
||||
}
|
||||
if ($reorder) {
|
||||
$newarray = array_values($newarray);
|
||||
}
|
||||
|
||||
return $newarray;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Searches a 2d array using a multiple words that match by separating by space.
|
||||
*
|
||||
* This function takes a 2D array, a column name, and a search string as input.
|
||||
* It separates the search string by space and calls the sqlarray_2dfilter function
|
||||
* multiple times until all words are matched.
|
||||
*
|
||||
* @param array $array The 2D array to search in.
|
||||
* @param string $columnname The name of the column to search in.
|
||||
* @param string $stringtosearch The search string to search for.
|
||||
* @param bool $exact Whether to search for exact matches or not. Defaults to FALSE.
|
||||
* @param bool $caseinsensitive Whether to perform a case-insensitive search or not. Defaults to FALSE.
|
||||
* @param bool $simplesearchwith_strpos Whether to use the strpos function for searching or not. Defaults to FALSE.
|
||||
*
|
||||
* @return array The filtered data array where all words in the search string are present.
|
||||
*/
|
||||
function sqlarray_2dfilter_multiple($array, $columnname, string $stringtosearch, $exact = FALSE, $caseinsensitive = false, $simplesearchwith_strpos = false)
|
||||
{
|
||||
if ($columnname === '' or $columnname === null or $columnname === false) {
|
||||
return FALSE;
|
||||
}
|
||||
if (!$array) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$words = explode(' ', $stringtosearch);
|
||||
$result = $array;
|
||||
|
||||
foreach ($words as $word) {
|
||||
$result = sqlarray_2dfilter($result, $columnname, $word, $exact, $caseinsensitive, $simplesearchwith_strpos);
|
||||
if (empty($result)) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function sqlarray_2dfilterContinuos($array, $columnname, $stringarraytosearch, $exact = FALSE, $caseinsensitive = false)
|
||||
{
|
||||
$res = [];
|
||||
foreach ($stringarraytosearch as $key => $value) {
|
||||
$searchresult = sqlarray_2dfilter($array, $columnname, $value, $exact, $caseinsensitive);
|
||||
if (!$searchresult) {
|
||||
continue;
|
||||
}
|
||||
$res = [...$res, ...$searchresult];
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
function encryptString($simple_string, $encryption_key)
|
||||
{
|
||||
$ciphering = "AES-128-CTR";
|
||||
$iv_length = openssl_cipher_iv_length($ciphering);
|
||||
$options = 0;
|
||||
$encryption_iv = '1218277893585121';
|
||||
$encryption = openssl_encrypt(
|
||||
$simple_string,
|
||||
$ciphering,
|
||||
$encryption_key,
|
||||
$options,
|
||||
$encryption_iv
|
||||
);
|
||||
return $encryption;
|
||||
|
||||
}
|
||||
|
||||
|
||||
function decryptString($string, $decryption_key)
|
||||
{
|
||||
$ciphering = "AES-128-CTR";
|
||||
$decryption_iv = '1218277893585121';
|
||||
$options = 0;
|
||||
$decryption = openssl_decrypt(
|
||||
$string,
|
||||
$ciphering,
|
||||
$decryption_key,
|
||||
$options,
|
||||
$decryption_iv
|
||||
);
|
||||
return $decryption;
|
||||
}
|
||||
|
||||
function objectToUrlSafeBase64($obj)
|
||||
{
|
||||
$jsonString = json_encode($obj);
|
||||
$base64String = base64_encode($jsonString);
|
||||
$urlSafeBase64 = str_replace(['+', '/', '='], ['-', '_', ''], $base64String);
|
||||
return $urlSafeBase64;
|
||||
}
|
||||
|
||||
function urlSafeBase64ToObject($urlSafeBase64)
|
||||
{
|
||||
$base64String = str_replace(['-', '_'], ['+', '/'], $urlSafeBase64);
|
||||
$padding = strlen($base64String) % 4;
|
||||
if ($padding) {
|
||||
$base64String .= str_repeat('=', 4 - $padding);
|
||||
}
|
||||
$jsonString = base64_decode($base64String);
|
||||
return json_decode($jsonString);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
56
app/Http/Controllers/Helpers/ObjectToBase64Helper.php
Normal file
56
app/Http/Controllers/Helpers/ObjectToBase64Helper.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Helpers;
|
||||
|
||||
|
||||
use App\Models\FileList;
|
||||
use Hypervel\Http\Request;
|
||||
use App\Http\Controllers\FilesMainController;
|
||||
|
||||
class ObjectToBase64Helper
|
||||
{
|
||||
|
||||
|
||||
public static function urlSafeBase64ToObject(string $urlSafeBase64, bool $compressed = true)
|
||||
{
|
||||
|
||||
if (!is_string($urlSafeBase64)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$base64 = str_replace(['-', '_'], ['+', '/'], $urlSafeBase64);
|
||||
|
||||
|
||||
$padding = strlen($base64) % 4;
|
||||
if ($padding !== 0) {
|
||||
$base64 .= str_repeat('=', 4 - $padding);
|
||||
}
|
||||
|
||||
// Base64 decode
|
||||
$binary = base64_decode($base64, true);
|
||||
if ($binary === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($compressed) {
|
||||
// Equivalent of pako.inflate()
|
||||
$json = gzuncompress($binary);
|
||||
if ($json === false) {
|
||||
return null;
|
||||
}
|
||||
return json_decode($json, true);
|
||||
}
|
||||
|
||||
return json_decode($binary, true);
|
||||
} catch (\Throwable $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
37
app/Http/Controllers/Helpers/PaymentProcessor.php
Normal file
37
app/Http/Controllers/Helpers/PaymentProcessor.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Helpers;
|
||||
|
||||
/**
|
||||
* Mock Payment Processor Helper for Co-op Credit Top-ups
|
||||
*/
|
||||
class PaymentProcessor
|
||||
{
|
||||
/**
|
||||
* Simulate a top-up request to an external processor
|
||||
*/
|
||||
public static function initiatePayment(float $amount, string $method, string $userHash)
|
||||
{
|
||||
// In a real scenario, this would call GCash/PayMaya API
|
||||
// For now, we return a mock response
|
||||
return [
|
||||
'success' => true,
|
||||
'transaction_id' => 'TXN-' . strtoupper(bin2hex(random_bytes(6))),
|
||||
'redirect_url' => null, // Would be used for hosted payment pages
|
||||
'status' => 'PENDING',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a payment after external confirmation
|
||||
*/
|
||||
public static function completePayment(string $transactionId)
|
||||
{
|
||||
return [
|
||||
'success' => true,
|
||||
'status' => 'COMPLETED',
|
||||
];
|
||||
}
|
||||
}
|
||||
191
app/Http/Controllers/Helpers/Permissions/ProductPermissions.php
Normal file
191
app/Http/Controllers/Helpers/Permissions/ProductPermissions.php
Normal file
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Helpers\Permissions;
|
||||
|
||||
use App\Enums\UserTypes;
|
||||
use Hypervel\Http\Request;
|
||||
use App\Models\User;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use App\Enums\UserActions;
|
||||
use App\Http\Controllers\Helpers\QueryHelper;
|
||||
use App\Models\Market\Product;
|
||||
use App\Models\Market\Store;
|
||||
use Exception;
|
||||
|
||||
class ProductPermissions
|
||||
{
|
||||
|
||||
|
||||
public static function isModificationAllowed()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static function isCreationAllowed()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static function isActionAllowed(
|
||||
UserActions $userAction,
|
||||
Product|string|int|null $productHashorID = null,
|
||||
Store|string|int|null $storeHashorID = null
|
||||
): bool {
|
||||
try {
|
||||
$acct_type = Auth::user()->acct_type;
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$defaultRoles = ProductPermissionsDefinition::getAllowedUserTypesAction($acct_type);
|
||||
$additionalRoles = UserPermissions::isUserAllowedbyAdditionalRoles($userAction);
|
||||
$deniedRoles = UserPermissions::isUserDeniedRoles($userAction);
|
||||
|
||||
if ($deniedRoles) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!in_array($userAction, $defaultRoles, true) && !$additionalRoles) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ProductPermissionsDefinition::doesActionRequireDirectChildren($userAction)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$storeHashorID && !$productHashorID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$store = null;
|
||||
$product = null;
|
||||
|
||||
if ($storeHashorID) {
|
||||
$store = QueryHelper::findOrNullByHashOrId($storeHashorID, Store::class);
|
||||
}
|
||||
|
||||
if ($productHashorID) {
|
||||
$product = QueryHelper::findOrNullByHashOrId($productHashorID, Product::class);
|
||||
}
|
||||
|
||||
if (!$store && !$product) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine store from product if needed
|
||||
if (!$store && $product) {
|
||||
$store = $product->store ?? null;
|
||||
}
|
||||
|
||||
if (!$store) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$storeOwner = $store->owner;
|
||||
if ($storeOwner && UserPermissions::isDescendantOfCurrentUser($storeOwner)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check all managers in the new store_managers table
|
||||
$managerIds = $store->managerUsers()->pluck('users.id')->toArray();
|
||||
foreach ($managerIds as $managerId) {
|
||||
if (UserPermissions::isDescendantOfCurrentUser($managerId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy manager check
|
||||
if ($store->manager_id && UserPermissions::isDescendantOfCurrentUser($store->manager_id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
class ProductPermissionsDefinition
|
||||
{
|
||||
|
||||
public static function getAllowedUserTypesAction(UserTypes $currentUserType)
|
||||
{
|
||||
return match ($currentUserType) {
|
||||
UserTypes::ULTIMATE => UserActions::cases(),
|
||||
|
||||
UserTypes::SUPER_OPERATOR => [
|
||||
UserActions::CreateStoreforSelf,
|
||||
UserActions::CreateStoreGlobal,
|
||||
UserActions::ModifyAllStores,
|
||||
UserActions::ModifyOwnStore,
|
||||
UserActions::CreateProductGlobal,
|
||||
UserActions::CreateProductForOwnStore,
|
||||
UserActions::CreateProductforSelf,
|
||||
UserActions::ModifyAllProducts,
|
||||
UserActions::ModifyOwnProduct,
|
||||
UserActions::AddProducttoOwnStore,
|
||||
UserActions::AddProducttoAnyStore,
|
||||
UserActions::RemoveProductfromAnyStore,
|
||||
],
|
||||
|
||||
UserTypes::OPERATOR => [
|
||||
UserActions::CreateStoreforSelf,
|
||||
UserActions::CreateStoreGlobal,
|
||||
UserActions::ModifyAllStores,
|
||||
UserActions::ModifyOwnStore,
|
||||
UserActions::CreateProductGlobal,
|
||||
UserActions::CreateProductForOwnStore,
|
||||
UserActions::CreateProductforSelf,
|
||||
UserActions::ModifyAllProducts,
|
||||
UserActions::ModifyOwnProduct,
|
||||
UserActions::AddProducttoOwnStore,
|
||||
UserActions::AddProducttoAnyStore,
|
||||
UserActions::RemoveProductfromAnyStore,
|
||||
],
|
||||
|
||||
UserTypes::STORE_OWNER => [
|
||||
UserActions::ModifyOwnStore,
|
||||
UserActions::ModifyOwnProduct,
|
||||
UserActions::AddProducttoOwnStore,
|
||||
UserActions::CreateProductForOwnStore,
|
||||
],
|
||||
|
||||
UserTypes::STORE_MANAGER => [
|
||||
UserActions::ModifyOwnProduct,
|
||||
UserActions::AddProducttoOwnStore,
|
||||
UserActions::CreateProductForOwnStore,
|
||||
],
|
||||
|
||||
default => [],
|
||||
};
|
||||
}
|
||||
|
||||
public static function doesActionRequireDirectChildren(UserActions $userAction)
|
||||
{
|
||||
return match ($userAction) {
|
||||
UserActions::CreateStoreforSelf => true,
|
||||
UserActions::CreateStoreGlobal => false,
|
||||
UserActions::ModifyAllStores => false,
|
||||
UserActions::ModifyOwnStore => true,
|
||||
UserActions::CreateProductGlobal => false,
|
||||
UserActions::CreateProductforSelf => true,
|
||||
UserActions::ModifyAllProducts => false,
|
||||
UserActions::ModifyOwnProduct => true,
|
||||
UserActions::AddProducttoOwnStore=>true,
|
||||
UserActions::AddProducttoAnyStore=>false,
|
||||
UserActions::RemoveProductfromAnyStore=>false,
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
1035
app/Http/Controllers/Helpers/Permissions/UserPermissions.php
Normal file
1035
app/Http/Controllers/Helpers/Permissions/UserPermissions.php
Normal file
File diff suppressed because it is too large
Load Diff
123
app/Http/Controllers/Helpers/PhotoURL.php
Normal file
123
app/Http/Controllers/Helpers/PhotoURL.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Helpers;
|
||||
|
||||
|
||||
use App\Models\FileList;
|
||||
use Hypervel\Http\Request;
|
||||
use App\Http\Controllers\FilesMainController;
|
||||
|
||||
class PhotoURL
|
||||
{
|
||||
public static function getDropzoneDetailsbyHashkey(string $filelist_hashkey): array|false
|
||||
{
|
||||
$fileList = FileList::where('hashkey', $filelist_hashkey)->first();
|
||||
if (!$fileList) {
|
||||
return false;
|
||||
}
|
||||
$fileContent = $fileList->fileContent()->first();
|
||||
$file_url = $fileList->resolvedUrl();
|
||||
$filename = $fileList->filename ?? $fileContent?->titlename ?? 'unknown_file';
|
||||
$thumbnail = null;
|
||||
return [
|
||||
'url' => $file_url,
|
||||
'name' => $filename,
|
||||
'size' => $fileContent->size_in_bytes ?? 0,
|
||||
'hashkey' => $fileList->hashkey,
|
||||
'thumbnail' => $thumbnail ?? $file_url,
|
||||
];
|
||||
}
|
||||
|
||||
public static function photoURLArraytoDropzoneData(array|false|null $PhotoURLArray)
|
||||
{
|
||||
if (empty($PhotoURLArray)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dropzoneArray = [];
|
||||
foreach ($PhotoURLArray as $hashkey) {
|
||||
$dropzoneArray[] = self::getDropzoneDetailsbyHashkey($hashkey) ?? [];
|
||||
|
||||
}
|
||||
return $dropzoneArray;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function generateThumbnailGD(string $sourcePath, string $destinationPath, int $width = 200, int $height = 200): bool
|
||||
{
|
||||
if (!file_exists($sourcePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$imageInfo = getimagesize($sourcePath);
|
||||
if (!$imageInfo) return false;
|
||||
|
||||
[$srcWidth, $srcHeight] = $imageInfo;
|
||||
$mime = $imageInfo['mime'];
|
||||
|
||||
// Create image resource from source
|
||||
switch ($mime) {
|
||||
case 'image/jpeg':
|
||||
$srcImage = imagecreatefromjpeg($sourcePath);
|
||||
break;
|
||||
case 'image/png':
|
||||
$srcImage = imagecreatefrompng($sourcePath);
|
||||
break;
|
||||
case 'image/gif':
|
||||
$srcImage = imagecreatefromgif($sourcePath);
|
||||
break;
|
||||
case 'image/webp':
|
||||
$srcImage = imagecreatefromwebp($sourcePath);
|
||||
break;
|
||||
default:
|
||||
return false; // unsupported
|
||||
}
|
||||
|
||||
// Calculate new dimensions preserving aspect ratio
|
||||
$aspectRatio = $srcWidth / $srcHeight;
|
||||
if ($width / $height > $aspectRatio) {
|
||||
$width = (int)($height * $aspectRatio);
|
||||
} else {
|
||||
$height = (int)($width / $aspectRatio);
|
||||
}
|
||||
|
||||
// Create the new thumbnail image
|
||||
$thumbImage = imagecreatetruecolor($width, $height);
|
||||
|
||||
// Preserve transparency for PNG/GIF
|
||||
if (in_array($mime, ['image/png', 'image/gif'])) {
|
||||
imagecolortransparent($thumbImage, imagecolorallocatealpha($thumbImage, 0, 0, 0, 127));
|
||||
imagealphablending($thumbImage, false);
|
||||
imagesavealpha($thumbImage, true);
|
||||
}
|
||||
|
||||
// Resize
|
||||
imagecopyresampled($thumbImage, $srcImage, 0, 0, 0, 0, $width, $height, $srcWidth, $srcHeight);
|
||||
|
||||
// Save to file
|
||||
switch ($mime) {
|
||||
case 'image/jpeg':
|
||||
imagejpeg($thumbImage, $destinationPath, 85);
|
||||
break;
|
||||
case 'image/png':
|
||||
imagepng($thumbImage, $destinationPath);
|
||||
break;
|
||||
case 'image/gif':
|
||||
imagegif($thumbImage, $destinationPath);
|
||||
break;
|
||||
case 'image/webp':
|
||||
imagewebp($thumbImage, $destinationPath, 85);
|
||||
break;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
imagedestroy($srcImage);
|
||||
imagedestroy($thumbImage);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
172
app/Http/Controllers/Helpers/QrphDecoder.php
Normal file
172
app/Http/Controllers/Helpers/QrphDecoder.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Helpers;
|
||||
|
||||
/**
|
||||
* Decoder for QRPH / EMV QRCPS strings.
|
||||
*
|
||||
* QRPH follows the EMVCo Merchant-Presented QR Code Specification.
|
||||
* Each field is encoded as: 2-char tag + 2-char length (decimal) + value.
|
||||
* Nested (sub-field) structures use the same encoding inside a parent value.
|
||||
*/
|
||||
class QrphDecoder
|
||||
{
|
||||
/**
|
||||
* Decode a QRPH string and return human-readable fields.
|
||||
*/
|
||||
public static function decode(string $qrString): array
|
||||
{
|
||||
$qrString = trim($qrString);
|
||||
$fields = self::parseTlv($qrString);
|
||||
|
||||
return [
|
||||
'raw' => $qrString,
|
||||
'valid' => self::validateCrc($qrString, $fields),
|
||||
'initiation_method'=> self::initiationLabel($fields['01'] ?? null),
|
||||
'merchant_name' => $fields['59'] ?? null,
|
||||
'merchant_city' => $fields['60'] ?? null,
|
||||
'country_code' => $fields['58'] ?? null,
|
||||
'currency' => $fields['53'] ?? null, // 608 = PHP
|
||||
'amount' => isset($fields['54']) ? (float) $fields['54'] : null,
|
||||
'merchant_account' => self::extractMerchantAccount($fields),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a flat TLV string into [ tag => value ] pairs.
|
||||
*/
|
||||
public static function parseTlv(string $data): array
|
||||
{
|
||||
$result = [];
|
||||
$pos = 0;
|
||||
$len = strlen($data);
|
||||
|
||||
while ($pos + 4 <= $len) {
|
||||
$tag = substr($data, $pos, 2);
|
||||
$length = (int) substr($data, $pos + 2, 2);
|
||||
$value = substr($data, $pos + 4, $length);
|
||||
|
||||
$result[$tag] = $value;
|
||||
$pos += 4 + $length;
|
||||
|
||||
if ($tag === '63') break; // CRC is always last
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract primary account info from merchant account fields (tags 26–51).
|
||||
* Returns the first one found with a recognisable GUID.
|
||||
*/
|
||||
private static function extractMerchantAccount(array $fields): ?array
|
||||
{
|
||||
for ($i = 26; $i <= 51; $i++) {
|
||||
$tag = str_pad((string) $i, 2, '0', STR_PAD_LEFT);
|
||||
if (!isset($fields[$tag])) continue;
|
||||
|
||||
$sub = self::parseTlv($fields[$tag]);
|
||||
$guid = $sub['00'] ?? '';
|
||||
|
||||
return [
|
||||
'guid' => $guid,
|
||||
'network' => self::networkFromGuid($guid),
|
||||
'account' => $sub['01'] ?? null,
|
||||
'name' => $sub['02'] ?? null,
|
||||
];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function networkFromGuid(string $guid): string
|
||||
{
|
||||
$map = [
|
||||
// Standard QRPH / InstaPay (BSP / PPMI)
|
||||
'com.p2pqrph' => 'InstaPay (QRPH)',
|
||||
'ph.ppmi' => 'InstaPay',
|
||||
'ph.bsp.qrph' => 'InstaPay (BSP)',
|
||||
// GoTyme Bank
|
||||
'com.gotyme' => 'GoTyme',
|
||||
'ph.gotyme' => 'GoTyme',
|
||||
'gotyme' => 'GoTyme',
|
||||
// GCash
|
||||
'com.gcash' => 'GCash',
|
||||
// Maya / PayMaya
|
||||
'com.paymaya' => 'Maya',
|
||||
'com.maya' => 'Maya',
|
||||
// Major PH banks
|
||||
'ph.bpi' => 'BPI',
|
||||
'ph.bdo' => 'BDO',
|
||||
'com.landbank' => 'Landbank',
|
||||
'ph.landbank' => 'Landbank',
|
||||
'ph.ubp' => 'UnionBank',
|
||||
'ph.metrobank' => 'Metrobank',
|
||||
'ph.rcbc' => 'RCBC',
|
||||
'ph.pnb' => 'PNB',
|
||||
'ph.securitybank' => 'Security Bank',
|
||||
'ph.chinabank' => 'China Bank',
|
||||
'ph.eastwest' => 'EastWest Bank',
|
||||
'ph.psbank' => 'PSBank',
|
||||
'ph.boc' => 'Bank of Commerce',
|
||||
'com.seabank' => 'SeaBank',
|
||||
'com.tonik' => 'Tonik',
|
||||
'com.uno' => 'UNO Digital Bank',
|
||||
'com.komo' => 'CIMB / Komo',
|
||||
'com.ownbank' => 'OwnBank',
|
||||
];
|
||||
|
||||
$lower = strtolower($guid);
|
||||
foreach ($map as $key => $label) {
|
||||
if (str_contains($lower, $key)) return $label;
|
||||
}
|
||||
|
||||
// Fallback: return the raw GUID so it's still visible
|
||||
return $guid ?: 'Unknown';
|
||||
}
|
||||
|
||||
private static function initiationLabel(?string $code): string
|
||||
{
|
||||
return match ($code) {
|
||||
'11' => 'Static',
|
||||
'12' => 'Dynamic',
|
||||
default => $code ?? 'Unknown',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the CRC-16/CCITT-FALSE checksum embedded in the QR string.
|
||||
*/
|
||||
public static function validateCrc(string $qrString, array $parsed = []): bool
|
||||
{
|
||||
if (empty($parsed['63'])) return false;
|
||||
|
||||
// CRC covers everything up to and including the "6304" tag+length prefix.
|
||||
$crcPos = strrpos($qrString, '6304');
|
||||
if ($crcPos === false) return false;
|
||||
|
||||
$payload = substr($qrString, 0, $crcPos + 4);
|
||||
$expected = strtoupper($parsed['63']);
|
||||
$computed = self::crc16($payload);
|
||||
|
||||
return $computed === $expected;
|
||||
}
|
||||
|
||||
/**
|
||||
* CRC-16/CCITT-FALSE (polynomial 0x1021, seed 0xFFFF).
|
||||
*/
|
||||
public static function crc16(string $data): string
|
||||
{
|
||||
$crc = 0xFFFF;
|
||||
for ($i = 0, $l = strlen($data); $i < $l; $i++) {
|
||||
$crc ^= (ord($data[$i]) << 8);
|
||||
for ($j = 0; $j < 8; $j++) {
|
||||
$crc = ($crc & 0x8000)
|
||||
? (($crc << 1) ^ 0x1021) & 0xFFFF
|
||||
: ($crc << 1) & 0xFFFF;
|
||||
}
|
||||
}
|
||||
return sprintf('%04X', $crc);
|
||||
}
|
||||
}
|
||||
42
app/Http/Controllers/Helpers/QueryHelper.php
Normal file
42
app/Http/Controllers/Helpers/QueryHelper.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Helpers;
|
||||
|
||||
|
||||
use App\Models\FileList;
|
||||
use Hypervel\Http\Request;
|
||||
use App\Http\Controllers\FilesMainController;
|
||||
use Hypervel\Database\Eloquent\Model;
|
||||
|
||||
class QueryHelper
|
||||
{
|
||||
|
||||
public static function findOrNullByHashOrId(
|
||||
int|string|Model|false $hashOrId,
|
||||
string $modelClass
|
||||
): false|Model {
|
||||
|
||||
if (!$hashOrId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if ($hashOrId instanceof Model) {
|
||||
return $hashOrId;
|
||||
}
|
||||
|
||||
|
||||
if (!is_subclass_of($modelClass, Model::class)) {
|
||||
throw new \InvalidArgumentException('Invalid model class');
|
||||
}
|
||||
|
||||
if (is_int($hashOrId)) {
|
||||
return $modelClass::find($hashOrId);
|
||||
}
|
||||
|
||||
return $modelClass::where('hashkey', $hashOrId)->first();
|
||||
}
|
||||
|
||||
}
|
||||
67
app/Http/Controllers/Helpers/ResponseHelper.php
Normal file
67
app/Http/Controllers/Helpers/ResponseHelper.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Helpers;
|
||||
|
||||
use Hypervel\Http\Request;
|
||||
|
||||
class ResponseHelper
|
||||
{
|
||||
public static function returnSuccessResponse($data, $hashkey,$message='')
|
||||
{
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $data,
|
||||
'hashkey' => $hashkey,
|
||||
'message'=>$message,
|
||||
]);
|
||||
}
|
||||
|
||||
public static function returnFalseOrNull($hashfromRequest)
|
||||
{
|
||||
if ($hashfromRequest) {
|
||||
return response()->json(['success' => false]);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static function returnUnauthorized()
|
||||
{
|
||||
return response()->json(['success' => false, 'message' => 'Unauthorized '], 403);
|
||||
}
|
||||
|
||||
public static function returnIncorrectDetails()
|
||||
{
|
||||
return self::returnError('Incomplete/Incorrect Details!');
|
||||
}
|
||||
|
||||
public static function returnError($errorText, $status = 500, $data = [])
|
||||
{
|
||||
return response()->json(['success' => false, 'message' => $errorText, 'data' => $data], $status);
|
||||
}
|
||||
|
||||
public static function getTargetHash()
|
||||
{
|
||||
$target = request()->input('target');
|
||||
if (!$target) {
|
||||
return null;
|
||||
}
|
||||
if (is_object($target) || is_array($target)) {
|
||||
try {
|
||||
$target = $target['target'];
|
||||
return $target;
|
||||
} catch (\Throwable $th) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return $target;
|
||||
}
|
||||
|
||||
public static function getTargetData()
|
||||
{
|
||||
$target = request()->input('data');
|
||||
return $target;
|
||||
}
|
||||
}
|
||||
78
app/Http/Controllers/Helpers/UserController.php
Normal file
78
app/Http/Controllers/Helpers/UserController.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Helpers;
|
||||
|
||||
use App\Models\User;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
|
||||
class UserController
|
||||
{
|
||||
public static function findUserIdByHash(?string $hash): ?int
|
||||
{
|
||||
if (!$hash || !is_string($hash)) {
|
||||
return null;
|
||||
}
|
||||
$user = User::where('hashkey', $hash)->first();
|
||||
return $user?->id;
|
||||
}
|
||||
|
||||
//TODO Test this function
|
||||
public static function getCurrentUserDescendants()
|
||||
{
|
||||
try {
|
||||
return Auth::user()->getAllDescendants();
|
||||
} catch (\Throwable $th) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getUserDescendantsbyHash(string $hashkey)
|
||||
{
|
||||
try {
|
||||
$user = User::where('hashkey', $hashkey)->firstOrFail();
|
||||
return $user->getAllDescendants();
|
||||
} catch (\Throwable $th) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function checkifUserHashisADescendantofCurrentUser(string $hashkey)
|
||||
{
|
||||
try {
|
||||
$currentUser = Auth::user();
|
||||
if (!$currentUser)
|
||||
return false;
|
||||
|
||||
return $currentUser
|
||||
->getAllDescendants()
|
||||
->pluck('hashkey')
|
||||
->contains($hashkey);
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function checkifUserHashisAChildofCurrentUser(string $hashkey)
|
||||
{
|
||||
try {
|
||||
$currentUser = Auth::user();
|
||||
|
||||
if (!$currentUser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return User::where('parentuid', $currentUser->id)
|
||||
->where('hashkey', $hashkey)
|
||||
->exists();
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
21
app/Http/Controllers/IndexController.php
Normal file
21
app/Http/Controllers/IndexController.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Hypervel\Http\Request;
|
||||
|
||||
class IndexController extends AbstractController
|
||||
{
|
||||
public function index(Request $request): array
|
||||
{
|
||||
$user = $request->input('user', 'Hypervel');
|
||||
$method = $request->getMethod();
|
||||
|
||||
return [
|
||||
'method' => $method,
|
||||
'message' => "Hello {$user}.",
|
||||
];
|
||||
}
|
||||
}
|
||||
184
app/Http/Controllers/LoginController.php
Normal file
184
app/Http/Controllers/LoginController.php
Normal file
@@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Cache;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use App\Models\User;
|
||||
use Hypervel\Support\Facades\Hash;
|
||||
use Hypervel\Support\Facades\Log;
|
||||
|
||||
use Hypervel\Support\Facades\Redis;
|
||||
|
||||
class LoginController
|
||||
{
|
||||
public function authenticate(Request $request): ResponseInterface
|
||||
{
|
||||
$credentials = $request->inputs(['mobile_number', 'password']);
|
||||
$keepalive = $request->input('keepalive');
|
||||
|
||||
if (!$credentials['mobile_number'] || !$credentials['password']) {
|
||||
return Response::json([
|
||||
'success' => false,
|
||||
'message' => 'Missing fields.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$candidates = self::phMobileVariants($credentials['mobile_number']);
|
||||
|
||||
$user = User::whereIn('mobile_number', $candidates)->first();
|
||||
|
||||
if (!$user) {
|
||||
return Response::json([
|
||||
'success' => false,
|
||||
'message' => 'Account not found.',
|
||||
], 401);
|
||||
}
|
||||
|
||||
Log::info('Login attempt', [
|
||||
'mobile_number' => $credentials['mobile_number'],
|
||||
'candidates' => $candidates,
|
||||
'user_found' => true,
|
||||
'active' => $user->active,
|
||||
'acct_type' => $user->acct_type->value ?? null,
|
||||
]);
|
||||
|
||||
if (!$user->active) {
|
||||
return Response::json([
|
||||
'success' => false,
|
||||
'message' => 'Account is inactive. Please contact support.',
|
||||
], 401);
|
||||
}
|
||||
|
||||
if ($user && Hash::check($credentials['password'], $user->password)) {
|
||||
|
||||
Auth::login($user); // or Auth::guard()->login($user)
|
||||
|
||||
$current_userHashkey = $user->hashkey;
|
||||
$sessionId = session()->getId();
|
||||
|
||||
|
||||
|
||||
|
||||
Redis::sadd("user_sessions:{$current_userHashkey}", $sessionId);
|
||||
// $request->session()->regenerate();
|
||||
|
||||
Log::info('KeepAlive Value ' . $keepalive);
|
||||
if ($keepalive === true || $keepalive === 'true') {
|
||||
self::setSessiontoKeepAlive();
|
||||
}
|
||||
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'message' => 'Login successful',
|
||||
]);
|
||||
}
|
||||
|
||||
return Response::json([
|
||||
'success' => false,
|
||||
'message' => 'Invalid credentials.',
|
||||
], 401);
|
||||
|
||||
}
|
||||
/**
|
||||
* Set or extends current session auth<br>
|
||||
* This Function is Not Working JWT Automatically sets based on config file ttl
|
||||
* @param string|bool $sessionid if false then get current sessionID
|
||||
* @param int|bool $aliveinseconds if true then consider it forever
|
||||
* @return null|bool // Time To Live TTL
|
||||
*/
|
||||
public static function setSessiontoKeepAlive(string|false $sessionId = false, int|bool $aliveinseconds = true)
|
||||
{
|
||||
$sessionId = $sessionId ?: session()->getId();
|
||||
if (!$sessionId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($aliveinseconds === true) {
|
||||
$aliveinseconds = 7889472; // 3 months
|
||||
}
|
||||
if ($aliveinseconds === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The redis session driver stores the key under the Redis connection prefix.
|
||||
// Cache::get/put uses a different prefix (cache store), so we use Redis::expire()
|
||||
// directly to extend the TTL of the existing session key without reading its value.
|
||||
$result = Redis::expire($sessionId, $aliveinseconds);
|
||||
if (!$result) {
|
||||
Log::warning('setSessiontoKeepAlive: session key not found in Redis, cannot extend TTL for: ' . $sessionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
$ttl = Redis::ttl($sessionId);
|
||||
Log::info('extended session: ' . $sessionId . ' for ' . $aliveinseconds . 's, TTL: ' . $ttl);
|
||||
return $ttl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build all plausible stored forms of a Philippine mobile number so the
|
||||
* caller can match whichever variant is in the DB. Variants returned:
|
||||
* - 09XXXXXXXXX
|
||||
* - 9XXXXXXXXX (no leading 0)
|
||||
* - 639XXXXXXXXX
|
||||
* - +639XXXXXXXXX
|
||||
* plus the original raw input as a final fallback.
|
||||
*/
|
||||
public static function phMobileVariants(string $input): array
|
||||
{
|
||||
$digits = preg_replace('/\D+/', '', $input);
|
||||
$core = null; // the 10-digit 9XXXXXXXXX portion
|
||||
|
||||
if (preg_match('/^639(\d{9})$/', $digits, $m)) {
|
||||
$core = '9' . $m[1];
|
||||
} elseif (preg_match('/^09(\d{9})$/', $digits, $m)) {
|
||||
$core = '9' . $m[1];
|
||||
} elseif (preg_match('/^9(\d{9})$/', $digits, $m)) {
|
||||
$core = '9' . $m[1];
|
||||
}
|
||||
|
||||
$variants = [$input];
|
||||
if ($core) {
|
||||
$variants[] = '0' . $core;
|
||||
$variants[] = $core;
|
||||
$variants[] = '63' . $core;
|
||||
$variants[] = '+63' . $core;
|
||||
}
|
||||
|
||||
return array_values(array_unique($variants));
|
||||
}
|
||||
|
||||
/**
|
||||
* Back-compat shim: still used by UserModifyAdminPageController to
|
||||
* canonicalize on save (admin edit). Picks 09XXXXXXXXX when possible.
|
||||
*/
|
||||
public static function normalizePhMobile(string $input): string
|
||||
{
|
||||
$digits = preg_replace('/\D+/', '', $input);
|
||||
|
||||
if (preg_match('/^639(\d{9})$/', $digits, $m)) {
|
||||
return '09' . $m[1];
|
||||
}
|
||||
if (preg_match('/^9(\d{9})$/', $digits, $m)) {
|
||||
return '09' . $m[1];
|
||||
}
|
||||
if (preg_match('/^09\d{9}$/', $digits)) {
|
||||
return $digits;
|
||||
}
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
public function extendcurrentSession()
|
||||
{
|
||||
$ttl = self::setSessiontoKeepAlive();
|
||||
|
||||
return Response::make('TTL is '.$ttl);
|
||||
|
||||
}
|
||||
}
|
||||
55
app/Http/Controllers/Market/ActivityController.php
Normal file
55
app/Http/Controllers/Market/ActivityController.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Market;
|
||||
|
||||
use App\Services\ActivityService;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
|
||||
class ActivityController
|
||||
{
|
||||
/**
|
||||
* Get recent activities.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
public function getRecent(Request $request)
|
||||
{
|
||||
$limit = (int) $request->input('limit', 10);
|
||||
$service = new ActivityService();
|
||||
$activities = $service->getRecentActivities($limit);
|
||||
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'data' => $activities
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search activities.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
*/
|
||||
public function search(Request $request)
|
||||
{
|
||||
$query = $request->input('q', '');
|
||||
$limit = (int) $request->input('limit', 20);
|
||||
$service = new ActivityService();
|
||||
|
||||
if (empty($query)) {
|
||||
$activities = $service->getRecentActivities($limit);
|
||||
} else {
|
||||
$activities = $service->searchActivities($query, $limit);
|
||||
}
|
||||
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'data' => $activities
|
||||
]);
|
||||
}
|
||||
}
|
||||
577
app/Http/Controllers/Market/BatchController.php
Normal file
577
app/Http/Controllers/Market/BatchController.php
Normal file
@@ -0,0 +1,577 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Market;
|
||||
|
||||
use App\Enums\UserActions;
|
||||
use App\Enums\UserTypes;
|
||||
use App\Http\Controllers\Helpers\Permissions\ProductPermissions;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserTypeService;
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Http\Controllers\Helpers\UserController;
|
||||
use App\Models\Market\CooperativeMember;
|
||||
use App\Models\Market\Organization;
|
||||
use App\Models\Market\Product;
|
||||
use App\Models\Market\Store;
|
||||
use App\Models\User;
|
||||
use Hypervel\Support\Str;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\DB;
|
||||
use Hypervel\Support\Facades\Hash;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use Hypervel\Support\Facades\Validator;
|
||||
|
||||
class BatchController
|
||||
{
|
||||
/**
|
||||
* Batch create products.
|
||||
* Available for Ultimate, Super Operator, and Operator.
|
||||
*/
|
||||
public function batchCreateProducts(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
$acctType = $user->acct_type instanceof UserTypes ? $user->acct_type : UserTypes::tryFrom($user->acct_type);
|
||||
$isBig3 = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR]);
|
||||
$isStoreOwner = $acctType === UserTypes::STORE_OWNER;
|
||||
|
||||
if (!$isBig3 && !$isStoreOwner) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$products = $request->input('products', []);
|
||||
if (empty($products)) {
|
||||
return ResponseHelper::returnError('No products provided');
|
||||
}
|
||||
|
||||
$targetStoreHash = $request->input('target_store_hash');
|
||||
$targetStore = null;
|
||||
if ($targetStoreHash) {
|
||||
$targetStore = Store::where('hashkey', $targetStoreHash)->first();
|
||||
if (!$targetStore) {
|
||||
return ResponseHelper::returnError('Target store not found');
|
||||
}
|
||||
}
|
||||
|
||||
// STORE_OWNER must operate against a store they actually own/manage.
|
||||
// Without that, batch-import has no destination and must be refused
|
||||
// up-front so the UI doesn't silently 401 partway through.
|
||||
if (!$isBig3) {
|
||||
if (!$targetStore) {
|
||||
return ResponseHelper::returnError('You must select one of your stores before importing products.', 422);
|
||||
}
|
||||
$ownsTarget = (int) $targetStore->owner_id === (int) $user->id
|
||||
|| (int) $targetStore->manager_id === (int) $user->id
|
||||
|| $targetStore->managers()->where('user_id', $user->id)->exists();
|
||||
if (!$ownsTarget) {
|
||||
return ResponseHelper::returnError('You can only import products into stores you own.', 403);
|
||||
}
|
||||
}
|
||||
|
||||
$results = [];
|
||||
$errors = [];
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
foreach ($products as $index => $productData) {
|
||||
$source = $productData['source'] ?? 'new';
|
||||
|
||||
if ($source === 'existing') {
|
||||
if (!$targetStore) {
|
||||
$errors[] = 'Row ' . ($index + 1) . ': A target store is required to import an existing product.';
|
||||
continue;
|
||||
}
|
||||
|
||||
$validator = Validator::make($productData, [
|
||||
'product_hash' => 'required|string',
|
||||
'price' => 'nullable|numeric|min:0',
|
||||
'available' => 'nullable|numeric',
|
||||
'description' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$errors[] = 'Row ' . ($index + 1) . ': ' . implode(', ', $validator->errors()->all());
|
||||
continue;
|
||||
}
|
||||
|
||||
$existing = Product::where('hashkey', $productData['product_hash'])->first();
|
||||
if (!$existing) {
|
||||
$errors[] = 'Row ' . ($index + 1) . ': Selected global product not found.';
|
||||
continue;
|
||||
}
|
||||
|
||||
$alreadyAttached = $targetStore->products()->where('prd_items.id', $existing->id)->exists();
|
||||
if ($alreadyAttached) {
|
||||
$errors[] = 'Row ' . ($index + 1) . ": '{$existing->name}' is already in the target store.";
|
||||
continue;
|
||||
}
|
||||
|
||||
$price = $productData['price'] ?? null;
|
||||
$price = ($price === null || $price === '' || (float) $price <= 0)
|
||||
? (float) $existing->price
|
||||
: (float) $price;
|
||||
|
||||
$description = $productData['description'] ?? '';
|
||||
if (trim((string) $description) === '') {
|
||||
$description = (string) ($existing->description ?? '');
|
||||
}
|
||||
|
||||
$available = (int) ($productData['available'] ?? 0);
|
||||
|
||||
$targetStore->products()->attach($existing->id, [
|
||||
'available' => $available,
|
||||
'price' => $price,
|
||||
'description' => $description,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$results[] = $existing->hashkey;
|
||||
continue;
|
||||
}
|
||||
|
||||
$validator = Validator::make($productData, [
|
||||
'name' => 'required|string|max:255',
|
||||
'price' => 'required|numeric|min:0',
|
||||
'available' => 'required|numeric',
|
||||
'unitname' => 'required|string|max:100',
|
||||
'description' => 'nullable|string',
|
||||
'category' => 'nullable|string|max:255',
|
||||
'subcategory' => 'nullable|string|max:255',
|
||||
'barcode' => 'nullable|string|max:255',
|
||||
'photourl' => 'nullable|array',
|
||||
'photourl.*' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$errors[] = "Row " . ($index + 1) . ": " . implode(', ', $validator->errors()->all());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reject if a global product with this name already exists.
|
||||
// Owners should pick the existing one via the fuzzy-search
|
||||
// modal instead of creating a duplicate global entry.
|
||||
$duplicate = Product::whereRaw('LOWER(TRIM(name)) = ?', [strtolower(trim((string) $productData['name']))])
|
||||
->first();
|
||||
if ($duplicate) {
|
||||
$errors[] = "Row " . ($index + 1) . ": '{$productData['name']}' already exists globally. Use 'Pick existing' to import it instead.";
|
||||
continue;
|
||||
}
|
||||
|
||||
$product = Product::create([
|
||||
'name' => $productData['name'],
|
||||
'description' => $productData['description'] ?? '',
|
||||
'price' => $productData['price'],
|
||||
'unitname' => $productData['unitname'],
|
||||
'available' => $productData['available'],
|
||||
'barcode' => $productData['barcode'] ?? null,
|
||||
'category' => $productData['category'] ?? null,
|
||||
'subcategory' => $productData['subcategory'] ?? null,
|
||||
'photourl' => $productData['photourl'] ?? [],
|
||||
'created_by' => $user->id,
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
if ($targetStore) {
|
||||
$targetStore->products()->attach($product->id, [
|
||||
'available' => (int) $productData['available'],
|
||||
'price' => (float) $productData['price'],
|
||||
'description' => $productData['description'] ?? '',
|
||||
'is_active' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
$results[] = $product->hashkey;
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
DB::rollBack();
|
||||
return Response::json(['success' => false, 'message' => 'Batch creation failed', 'errors' => $errors], 422);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
return Response::json(['success' => true, 'count' => count($results), 'data' => $results]);
|
||||
} catch (\Throwable $th) {
|
||||
DB::rollBack();
|
||||
return ResponseHelper::returnError($th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serve the batch products Excel template as a file download.
|
||||
* Available for all authenticated users with batch module access.
|
||||
*/
|
||||
public function downloadProductTemplate()
|
||||
{
|
||||
$templatePath = BASE_PATH . '/resources/templates/batch-products-template.xlsx';
|
||||
|
||||
if (!file_exists($templatePath)) {
|
||||
return Response::json(['success' => false, 'message' => 'Template file not found.'], 404);
|
||||
}
|
||||
|
||||
$fileContent = file_get_contents($templatePath);
|
||||
$filename = 'bukidbounty-batch-products-template.xlsx';
|
||||
|
||||
return Response::make($fileContent, 200, [
|
||||
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
|
||||
'Content-Length' => strlen($fileContent),
|
||||
'Cache-Control' => 'no-cache, no-store, must-revalidate',
|
||||
'Pragma' => 'no-cache',
|
||||
'Expires' => '0',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch create stores.
|
||||
* Available for Ultimate, Super Operator, and Operator.
|
||||
*/
|
||||
public function batchCreateStores(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
$acctType = $user->acct_type instanceof UserTypes ? $user->acct_type : UserTypes::tryFrom($user->acct_type);
|
||||
$isBig3 = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR]);
|
||||
|
||||
if (!$isBig3) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$stores = $request->input('stores', []);
|
||||
if (empty($stores)) {
|
||||
return ResponseHelper::returnError('No stores provided');
|
||||
}
|
||||
|
||||
$results = [];
|
||||
$errors = [];
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
foreach ($stores as $index => $storeData) {
|
||||
$validator = Validator::make($storeData, [
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'required|string',
|
||||
'address' => 'required|string',
|
||||
'category' => 'nullable|string|max:100',
|
||||
'subcategory' => 'nullable|string|max:100',
|
||||
'owner_hash' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$errors[] = "Row " . ($index + 1) . ": " . implode(', ', $validator->errors()->all());
|
||||
continue;
|
||||
}
|
||||
|
||||
$ownerId = null;
|
||||
if (!empty($storeData['owner_hash'])) {
|
||||
$ownerId = UserController::findUserIdByHash($storeData['owner_hash']);
|
||||
}
|
||||
|
||||
$storeCode = StoreController::generateStoreCode($storeData['category'] ?? 'General');
|
||||
|
||||
$store = Store::create([
|
||||
'storecode' => $storeCode,
|
||||
'name' => $storeData['name'],
|
||||
'description' => $storeData['description'],
|
||||
'address' => $storeData['address'],
|
||||
'category' => $storeData['category'] ?? 'General',
|
||||
'subcategory' => $storeData['subcategory'] ?? '',
|
||||
'owner_id' => $ownerId,
|
||||
'created_by' => $user->id,
|
||||
'is_active' => true,
|
||||
'status' => 'active',
|
||||
]);
|
||||
|
||||
$results[] = $store->hashkey;
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
DB::rollBack();
|
||||
return Response::json(['success' => false, 'message' => 'Batch creation failed', 'errors' => $errors], 422);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
return Response::json(['success' => true, 'count' => count($results), 'data' => $results]);
|
||||
} catch (\Throwable $th) {
|
||||
DB::rollBack();
|
||||
return ResponseHelper::returnError($th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch create users.
|
||||
* Available for Ultimate, Super Operator, and Operator.
|
||||
*/
|
||||
public function batchCreateUsers(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
$acctType = $user->acct_type instanceof UserTypes ? $user->acct_type : UserTypes::tryFrom($user->acct_type);
|
||||
$isBig3 = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR]);
|
||||
|
||||
if (!$isBig3) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$usersData = $request->input('users', []);
|
||||
if (empty($usersData)) {
|
||||
return ResponseHelper::returnError('No users provided');
|
||||
}
|
||||
|
||||
$results = [];
|
||||
$errors = [];
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
foreach ($usersData as $index => $data) {
|
||||
$validator = Validator::make($data, [
|
||||
'username' => 'required|string|max:255|unique:users,username',
|
||||
'name' => 'required|string|max:255',
|
||||
'mobile_number' => ['required', 'string', 'max:20', 'unique:users,mobile_number', 'regex:/^(09|\+639)\d{9}$/'],
|
||||
'password' => 'required|string|min:6',
|
||||
'type' => 'required|string',
|
||||
'parent_hash' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$errors[] = "Row " . ($index + 1) . ": " . implode(', ', $validator->errors()->all());
|
||||
continue;
|
||||
}
|
||||
|
||||
$usertypeEnum = UserTypes::tryFrom($data['type']);
|
||||
if (!$usertypeEnum) {
|
||||
$errors[] = "Row " . ($index + 1) . ": Invalid User Type";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if creator is allowed to create this user type
|
||||
if ($acctType !== UserTypes::ULTIMATE) {
|
||||
$allowedTypes = UserTypeService::getAllowedUserTypes($acctType);
|
||||
if (!in_array($usertypeEnum, $allowedTypes)) {
|
||||
$errors[] = "Row " . ($index + 1) . ": You are not allowed to create user type '{$data['type']}'";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$parentId = $user->id; // Default to creator
|
||||
if (!empty($data['parent_hash'])) {
|
||||
$parent = User::where('hashkey', $data['parent_hash'])->first();
|
||||
if ($parent) {
|
||||
$parentId = $parent->id;
|
||||
}
|
||||
}
|
||||
|
||||
$newUser = User::create([
|
||||
'username' => $data['username'],
|
||||
'name' => $data['name'],
|
||||
'mobile_number' => $data['mobile_number'],
|
||||
'password' => Hash::make($data['password']),
|
||||
'acct_type' => $data['type'],
|
||||
'parentuid' => $parentId,
|
||||
'active' => true,
|
||||
]);
|
||||
|
||||
$results[] = $newUser->hashkey;
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
DB::rollBack();
|
||||
return Response::json(['success' => false, 'message' => 'Batch creation failed', 'errors' => $errors], 422);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
return Response::json(['success' => true, 'count' => count($results), 'data' => $results]);
|
||||
} catch (\Throwable $th) {
|
||||
DB::rollBack();
|
||||
return ResponseHelper::returnError($th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch create cooperatives.
|
||||
* Available for Ultimate and Super Operator.
|
||||
*/
|
||||
public function batchCreateCooperatives(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
$acctType = $user->acct_type instanceof UserTypes ? $user->acct_type : UserTypes::tryFrom($user->acct_type);
|
||||
$isAllowed = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR]);
|
||||
|
||||
if (!$isAllowed) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$cooperatives = $request->input('cooperatives', []);
|
||||
if (empty($cooperatives)) {
|
||||
return ResponseHelper::returnError('No cooperatives provided');
|
||||
}
|
||||
|
||||
$results = [];
|
||||
$errors = [];
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
foreach ($cooperatives as $index => $data) {
|
||||
$validator = Validator::make($data, [
|
||||
'name' => 'required|string|max:255',
|
||||
'address' => 'nullable|string',
|
||||
'registration_number' => 'nullable|string|max:255',
|
||||
'cin' => 'nullable|string|max:255',
|
||||
'tin' => 'nullable|string|max:255',
|
||||
'cooperative_type' => 'nullable|string|max:100',
|
||||
'cooperative_category' => 'nullable|string|max:100',
|
||||
'registration_date' => 'nullable|date',
|
||||
'contact_person' => 'nullable|string|max:255',
|
||||
'contact_number' => 'nullable|string|max:50',
|
||||
'contact_email' => 'nullable|email|max:255',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$errors[] = "Row " . ($index + 1) . ": " . implode(', ', $validator->errors()->all());
|
||||
continue;
|
||||
}
|
||||
|
||||
$cooperative = new Organization([
|
||||
'hashkey' => Str::random(64),
|
||||
'name' => trim($data['name']),
|
||||
'type' => 'COOPERATIVE',
|
||||
'address' => trim($data['address'] ?? ''),
|
||||
'registration_number' => trim($data['registration_number'] ?? ''),
|
||||
'cin' => trim($data['cin'] ?? ''),
|
||||
'tin' => trim($data['tin'] ?? ''),
|
||||
'cooperative_type' => trim($data['cooperative_type'] ?? ''),
|
||||
'cooperative_category' => trim($data['cooperative_category'] ?? ''),
|
||||
'registration_date' => $data['registration_date'] ?? null,
|
||||
'contact_person' => trim($data['contact_person'] ?? ''),
|
||||
'contact_number' => trim($data['contact_number'] ?? ''),
|
||||
'contact_email' => trim($data['contact_email'] ?? ''),
|
||||
'is_active' => true,
|
||||
'created_by' => $user->id,
|
||||
]);
|
||||
|
||||
if (!$cooperative->save()) {
|
||||
$errors[] = "Row " . ($index + 1) . ": Failed to save cooperative";
|
||||
continue;
|
||||
}
|
||||
|
||||
$results[] = $cooperative->hashkey;
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
DB::rollBack();
|
||||
return Response::json(['success' => false, 'message' => 'Batch creation failed', 'errors' => $errors], 422);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
return Response::json(['success' => true, 'count' => count($results), 'data' => $results]);
|
||||
} catch (\Throwable $th) {
|
||||
DB::rollBack();
|
||||
return ResponseHelper::returnError($th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch add cooperative members with automatic user account creation.
|
||||
* Each row creates a new User and a corresponding CooperativeMember in one transaction.
|
||||
*/
|
||||
public function batchCreateCooperativeMembers(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
if (!UserPermissions::isActionPermitted($user->acct_type, UserActions::ManageOrganizations)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$cooperativeHash = $request->input('cooperative_hash');
|
||||
$members = $request->input('members', []);
|
||||
|
||||
if (!$cooperativeHash) {
|
||||
return ResponseHelper::returnError('Cooperative hash is required');
|
||||
}
|
||||
if (empty($members)) {
|
||||
return ResponseHelper::returnError('No members provided');
|
||||
}
|
||||
|
||||
$cooperative = Organization::where('hashkey', $cooperativeHash)
|
||||
->where('type', 'COOPERATIVE')
|
||||
->first();
|
||||
|
||||
if (!$cooperative) {
|
||||
return ResponseHelper::returnError('Cooperative not found', 404);
|
||||
}
|
||||
|
||||
$parentUser = User::where('id', $cooperative->created_by)->first() ?? $user;
|
||||
|
||||
$results = [];
|
||||
$errors = [];
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
foreach ($members as $index => $data) {
|
||||
$validator = Validator::make($data, [
|
||||
'username' => 'required|string|max:255|unique:users,username',
|
||||
'name' => 'required|string|max:255',
|
||||
'mobile_number' => ['required', 'string', 'max:20', 'unique:users,mobile_number', 'regex:/^(09|\+639)\d{9}$/'],
|
||||
'password' => 'required|string|min:6',
|
||||
'role' => 'nullable|string|max:50',
|
||||
'membership_type' => 'nullable|string|max:50',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$errors[] = 'Row ' . ($index + 1) . ': ' . implode(', ', $validator->errors()->all());
|
||||
continue;
|
||||
}
|
||||
|
||||
$newUser = new User();
|
||||
$newUser->username = $data['username'];
|
||||
$newUser->name = $data['name'];
|
||||
$newUser->mobile_number = $data['mobile_number'];
|
||||
$newUser->password = Hash::make($data['password']);
|
||||
$newUser->parentuid = $parentUser->id;
|
||||
$newUser->acct_type = 'user';
|
||||
$newUser->active = true;
|
||||
$newUser->save();
|
||||
|
||||
$member = new CooperativeMember([
|
||||
'hashkey' => Str::random(64),
|
||||
'organization_id' => $cooperative->id,
|
||||
'user_id' => $newUser->id,
|
||||
'role' => $data['role'] ?? 'MEMBER',
|
||||
'membership_type' => $data['membership_type'] ?? null,
|
||||
'joined_at' => now(),
|
||||
'is_active' => true,
|
||||
'created_by' => $user->id,
|
||||
]);
|
||||
|
||||
if (!$member->save()) {
|
||||
$errors[] = 'Row ' . ($index + 1) . ': Failed to save membership';
|
||||
continue;
|
||||
}
|
||||
|
||||
$results[] = [
|
||||
'user_hashkey' => $newUser->hashkey,
|
||||
'member_hashkey' => $member->hashkey,
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($errors)) {
|
||||
DB::rollBack();
|
||||
return Response::json(['success' => false, 'message' => 'Batch creation failed', 'errors' => $errors], 422);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
return Response::json(['success' => true, 'count' => count($results), 'data' => $results]);
|
||||
} catch (\Throwable $th) {
|
||||
DB::rollBack();
|
||||
return ResponseHelper::returnError($th->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
142
app/Http/Controllers/Market/CartController.php
Normal file
142
app/Http/Controllers/Market/CartController.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Market;
|
||||
|
||||
use App\Models\Market\Cart;
|
||||
use App\Models\Market\CartItem;
|
||||
use App\Models\Market\Product;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use Hypervel\Support\Str;
|
||||
|
||||
class CartController
|
||||
{
|
||||
public function getCart()
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) {
|
||||
return Response::json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$cart = Cart::firstOrCreate(['user_id' => $user->id]);
|
||||
|
||||
$items = $cart->items()->with('product')->get();
|
||||
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'cart' => $cart,
|
||||
'items' => $items,
|
||||
'total' => $items->sum(fn($item) => $item->price * $item->quantity)
|
||||
]);
|
||||
}
|
||||
|
||||
public function addItem(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) {
|
||||
return Response::json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'product_hash' => 'required|string',
|
||||
'quantity' => 'nullable|integer|min:1',
|
||||
]);
|
||||
|
||||
$product = Product::where('hashkey', $request->input('product_hash'))->first();
|
||||
if (!$product) {
|
||||
return Response::json(['error' => 'Product not found'], 404);
|
||||
}
|
||||
|
||||
$cart = Cart::firstOrCreate(['user_id' => $user->id]);
|
||||
|
||||
$item = $cart->items()->where('product_id', $product->id)->first();
|
||||
|
||||
if ($item) {
|
||||
$item->quantity += $request->input('quantity', 1);
|
||||
$item->save();
|
||||
} else {
|
||||
$cart->items()->create([
|
||||
'product_id' => $product->id,
|
||||
'quantity' => $request->input('quantity', 1),
|
||||
'price' => $product->price,
|
||||
'is_active' => true,
|
||||
'hashkey' => Str::uuid()->toString(),
|
||||
]);
|
||||
}
|
||||
|
||||
return Response::json(['success' => true, 'message' => 'Item added to cart']);
|
||||
}
|
||||
|
||||
public function updateItem(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) {
|
||||
return Response::json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'item_hash' => 'required|string',
|
||||
'quantity' => 'required|integer|min:1',
|
||||
]);
|
||||
|
||||
$item = CartItem::where('hashkey', $request->input('item_hash'))->first();
|
||||
if (!$item) {
|
||||
return Response::json(['error' => 'Item not found'], 404);
|
||||
}
|
||||
|
||||
// Verify cart ownership
|
||||
$cart = Cart::find($item->cart_id);
|
||||
if ($cart->user_id !== $user->id) {
|
||||
return Response::json(['error' => 'Forbidden'], 403);
|
||||
}
|
||||
|
||||
$item->quantity = $request->input('quantity');
|
||||
$item->save();
|
||||
|
||||
return Response::json(['success' => true, 'message' => 'Cart updated']);
|
||||
}
|
||||
|
||||
public function removeItem(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) {
|
||||
return Response::json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$request->validate([
|
||||
'item_hash' => 'required|string',
|
||||
]);
|
||||
|
||||
$item = CartItem::where('hashkey', $request->input('item_hash'))->first();
|
||||
if (!$item) {
|
||||
return Response::json(['error' => 'Item not found'], 404);
|
||||
}
|
||||
|
||||
$cart = Cart::find($item->cart_id);
|
||||
if ($cart->user_id !== $user->id) {
|
||||
return Response::json(['error' => 'Forbidden'], 403);
|
||||
}
|
||||
|
||||
$item->delete();
|
||||
|
||||
return Response::json(['success' => true, 'message' => 'Item removed from cart']);
|
||||
}
|
||||
|
||||
public function clearCart()
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) {
|
||||
return Response::json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$cart = Cart::where('user_id', $user->id)->first();
|
||||
if ($cart) {
|
||||
$cart->items()->delete();
|
||||
}
|
||||
|
||||
return Response::json(['success' => true, 'message' => 'Cart cleared']);
|
||||
}
|
||||
}
|
||||
470
app/Http/Controllers/Market/CooperativeController.php
Normal file
470
app/Http/Controllers/Market/CooperativeController.php
Normal file
@@ -0,0 +1,470 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Market;
|
||||
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\Market\CooperativeMember;
|
||||
use App\Models\Market\Organization;
|
||||
use App\Models\User;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\Hash;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Enums\UserActions;
|
||||
use App\Enums\UserTypes;
|
||||
use Hypervel\Support\Str;
|
||||
|
||||
class CooperativeController
|
||||
{
|
||||
public function listCooperatives(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewOrganizations)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$query = Organization::where('is_active', true)->where('type', 'COOPERATIVE');
|
||||
|
||||
$cooperatives = $query->withCount('members')->get();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $cooperatives
|
||||
]);
|
||||
}
|
||||
|
||||
public function getCooperative(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewOrganizations)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$hashkey = $request->input('hashkey');
|
||||
if (!$hashkey) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$cooperative = Organization::where('hashkey', $hashkey)->with(['members.user.userInfo'])->first();
|
||||
|
||||
if (!$cooperative) {
|
||||
return ResponseHelper::returnError('Cooperative not found', 404);
|
||||
}
|
||||
|
||||
$currentUserMembership = null;
|
||||
$user = Auth::user();
|
||||
if ($user) {
|
||||
$currentUserMembership = CooperativeMember::where('organization_id', $cooperative->id)
|
||||
->where('user_id', $user->id)
|
||||
->first();
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $cooperative,
|
||||
'is_member' => $currentUserMembership !== null,
|
||||
'membership' => $currentUserMembership,
|
||||
]);
|
||||
}
|
||||
|
||||
public function createCooperative(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::CreateOrganization)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$name = $request->input('name');
|
||||
$address = $request->input('address', '');
|
||||
$registrationNumber = $request->input('registration_number', '');
|
||||
$cin = $request->input('cin', '');
|
||||
$tin = $request->input('tin', '');
|
||||
$cooperativeType = $request->input('cooperative_type', '');
|
||||
$cooperativeCategory = $request->input('cooperative_category', '');
|
||||
$registrationDate = $request->input('registration_date', null);
|
||||
$contactPerson = $request->input('contact_person', '');
|
||||
$contactNumber = $request->input('contact_number', '');
|
||||
$contactEmail = $request->input('contact_email', '');
|
||||
|
||||
if (empty(trim($name ?? ''))) {
|
||||
return ResponseHelper::returnError('Cooperative name is required');
|
||||
}
|
||||
|
||||
$cooperative = new Organization([
|
||||
'hashkey' => Str::random(64),
|
||||
'name' => trim($name),
|
||||
'type' => 'COOPERATIVE',
|
||||
'address' => trim($address ?? ''),
|
||||
'registration_number' => trim($registrationNumber),
|
||||
'cin' => trim($cin),
|
||||
'tin' => trim($tin),
|
||||
'cooperative_type' => trim($cooperativeType),
|
||||
'cooperative_category' => trim($cooperativeCategory),
|
||||
'registration_date' => $registrationDate,
|
||||
'contact_person' => trim($contactPerson),
|
||||
'contact_number' => trim($contactNumber),
|
||||
'contact_email' => trim($contactEmail),
|
||||
'is_active' => true,
|
||||
'created_by' => Auth::id(),
|
||||
]);
|
||||
|
||||
if ($cooperative->save()) {
|
||||
return ResponseHelper::returnSuccessResponse($cooperative, $cooperative->hashkey, 'Cooperative created successfully');
|
||||
}
|
||||
|
||||
return ResponseHelper::returnError('Failed to create cooperative');
|
||||
}
|
||||
|
||||
public function joinCooperative(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$cooperativeHash = $request->input('cooperative_hash');
|
||||
if (!$cooperativeHash) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$cooperative = Organization::where('hashkey', $cooperativeHash)->first();
|
||||
if (!$cooperative) {
|
||||
return ResponseHelper::returnError('Cooperative not found', 404);
|
||||
}
|
||||
|
||||
// Check if already a member
|
||||
$existing = CooperativeMember::where('organization_id', $cooperative->id)
|
||||
->where('user_id', $user->id)
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
return ResponseHelper::returnError('Already a member of this cooperative');
|
||||
}
|
||||
|
||||
$memberFields = $request->only([
|
||||
'role', 'membership_type', 'membership_level', 'officer_position', 'officer_level',
|
||||
'concurrent_position', 'concurrent_level', 'cooperative_name_alt', 'cooperative_position', 'year_beginning',
|
||||
'priority_sector', 'common_bond', 'vulnerability_classifications',
|
||||
'philsys_id', 'sss_number', 'pagibig_number',
|
||||
'slp_track', 'slp_association_name', 'listahanan_id', 'fourtps_household_id',
|
||||
'tupad_category', 'tupad_insurance_beneficiary_name', 'tupad_insurance_beneficiary_relation',
|
||||
'preferred_occupation', 'nsrp_skills', 'employment_status', 'program_participation',
|
||||
]);
|
||||
|
||||
$member = new CooperativeMember(array_merge($memberFields, [
|
||||
'organization_id' => $cooperative->id,
|
||||
'user_id' => $user->id,
|
||||
'role' => $request->input('role', 'MEMBER'),
|
||||
'joined_at' => now(),
|
||||
'is_active' => true,
|
||||
]));
|
||||
|
||||
if ($member->save()) {
|
||||
// Sync with user settings
|
||||
$settings = $user->settings ?? [];
|
||||
$cooperatives = $settings['cooperatives'] ?? [];
|
||||
if (!in_array($cooperativeHash, $cooperatives)) {
|
||||
$cooperatives[] = $cooperativeHash;
|
||||
$settings['cooperatives'] = $cooperatives;
|
||||
$user->settings = $settings;
|
||||
$user->save();
|
||||
}
|
||||
return ResponseHelper::returnSuccessResponse($member, $member->hashkey, 'Successfully joined cooperative');
|
||||
}
|
||||
|
||||
return ResponseHelper::returnError('Failed to join cooperative');
|
||||
}
|
||||
|
||||
public function addMember(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageOrganizations)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$cooperativeHash = $request->input('cooperative_hash');
|
||||
$userHash = $request->input('user_hash');
|
||||
|
||||
if (!$cooperativeHash || !$userHash) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$cooperative = Organization::where('hashkey', $cooperativeHash)->first();
|
||||
$targetUser = User::where('hashkey', $userHash)->first();
|
||||
|
||||
if (!$cooperative || !$targetUser) {
|
||||
return ResponseHelper::returnError('Cooperative or User not found', 404);
|
||||
}
|
||||
|
||||
$existing = CooperativeMember::where('organization_id', $cooperative->id)
|
||||
->where('user_id', $targetUser->id)
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
return ResponseHelper::returnError('User is already a member');
|
||||
}
|
||||
|
||||
$memberFields = $request->only([
|
||||
'role', 'membership_type', 'membership_level', 'officer_position', 'officer_level',
|
||||
'concurrent_position', 'concurrent_level', 'cooperative_name_alt', 'cooperative_position', 'year_beginning',
|
||||
'priority_sector', 'common_bond', 'vulnerability_classifications',
|
||||
'philsys_id', 'sss_number', 'pagibig_number',
|
||||
'slp_track', 'slp_association_name', 'listahanan_id', 'fourtps_household_id',
|
||||
'tupad_category', 'tupad_insurance_beneficiary_name', 'tupad_insurance_beneficiary_relation',
|
||||
'preferred_occupation', 'nsrp_skills', 'employment_status', 'program_participation',
|
||||
]);
|
||||
|
||||
$member = new CooperativeMember(array_merge($memberFields, [
|
||||
'organization_id' => $cooperative->id,
|
||||
'user_id' => $targetUser->id,
|
||||
'role' => $request->input('role', 'MEMBER'),
|
||||
'joined_at' => now(),
|
||||
'is_active' => true,
|
||||
]));
|
||||
|
||||
if ($member->save()) {
|
||||
return ResponseHelper::returnSuccessResponse($member, $member->hashkey, 'Member added to cooperative');
|
||||
}
|
||||
|
||||
return ResponseHelper::returnError('Failed to add member');
|
||||
}
|
||||
|
||||
public function updateMember(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageOrganizations)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$memberHash = $request->input('member_hash');
|
||||
if (!$memberHash) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$member = CooperativeMember::where('hashkey', $memberHash)->first();
|
||||
if (!$member) {
|
||||
return ResponseHelper::returnError('Member record not found', 404);
|
||||
}
|
||||
|
||||
$memberFields = $request->only([
|
||||
'role', 'membership_type', 'membership_level', 'officer_position', 'officer_level',
|
||||
'concurrent_position', 'concurrent_level', 'cooperative_name_alt', 'cooperative_position', 'year_beginning',
|
||||
'is_active'
|
||||
]);
|
||||
|
||||
$member->fill($memberFields);
|
||||
|
||||
if ($member->save()) {
|
||||
return ResponseHelper::returnSuccessResponse($member, $member->hashkey, 'Membership details updated');
|
||||
}
|
||||
|
||||
return ResponseHelper::returnError('Failed to update membership details');
|
||||
}
|
||||
|
||||
public function registerMember(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
if (!UserPermissions::isActionPermitted($user->acct_type, UserActions::JoinCooperative)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$cooperativeHash = $request->input('cooperative_hash');
|
||||
if (!$cooperativeHash) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$cooperative = Organization::where('hashkey', $cooperativeHash)->first();
|
||||
if (!$cooperative) {
|
||||
return ResponseHelper::returnError('Cooperative not found', 404);
|
||||
}
|
||||
|
||||
// Check if already a member
|
||||
$existing = CooperativeMember::where('organization_id', $cooperative->id)
|
||||
->where('user_id', $user->id)
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
return ResponseHelper::returnError('Already a member of this cooperative');
|
||||
}
|
||||
|
||||
$memberFields = $request->only([
|
||||
'role', 'membership_type', 'membership_level', 'officer_position', 'officer_level',
|
||||
'concurrent_position', 'concurrent_level', 'cooperative_name_alt', 'cooperative_position', 'year_beginning',
|
||||
'priority_sector', 'common_bond', 'vulnerability_classifications',
|
||||
'philsys_id', 'sss_number', 'pagibig_number',
|
||||
'slp_track', 'slp_association_name', 'listahanan_id', 'fourtps_household_id',
|
||||
'tupad_category', 'tupad_insurance_beneficiary_name', 'tupad_insurance_beneficiary_relation',
|
||||
'preferred_occupation', 'nsrp_skills', 'employment_status', 'program_participation',
|
||||
]);
|
||||
|
||||
$member = new CooperativeMember(array_merge($memberFields, [
|
||||
'hashkey' => Str::random(64),
|
||||
'organization_id' => $cooperative->id,
|
||||
'user_id' => $user->id,
|
||||
'role' => $request->input('role', 'MEMBER'),
|
||||
'joined_at' => now(),
|
||||
'is_active' => true,
|
||||
'created_by' => $user->id,
|
||||
]));
|
||||
|
||||
if ($member->save()) {
|
||||
// Sync with user settings
|
||||
$settings = $user->settings ?? [];
|
||||
$cooperatives = $settings['cooperatives'] ?? [];
|
||||
if (!is_array($cooperatives)) $cooperatives = [];
|
||||
|
||||
if (!in_array($cooperativeHash, $cooperatives)) {
|
||||
$cooperatives[] = $cooperativeHash;
|
||||
$settings['cooperatives'] = $cooperatives;
|
||||
$user->settings = $settings;
|
||||
$user->save();
|
||||
}
|
||||
// Upgrade a plain USER to COOP_MEMBER on cooperative registration.
|
||||
// Never downgrade a higher type (COORDINATOR, OPERATOR, etc.).
|
||||
if ($user->acct_type === UserTypes::USER) {
|
||||
$user->acct_type = UserTypes::COOP_MEMBER;
|
||||
$user->save();
|
||||
}
|
||||
return ResponseHelper::returnSuccessResponse($member, $member->hashkey, 'Successfully registered as a cooperative member');
|
||||
}
|
||||
|
||||
return ResponseHelper::returnError('Failed to register');
|
||||
}
|
||||
|
||||
public function publicCompleteMembership(Request $request)
|
||||
{
|
||||
$userHashkey = $request->input('user_hashkey');
|
||||
$coopHash = $request->input('cooperative_hash');
|
||||
|
||||
if (!$userHashkey || !$coopHash) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$user = User::where('hashkey', $userHashkey)->first();
|
||||
if (!$user) {
|
||||
return response()->json(['success' => false, 'message' => 'User not found'], 404);
|
||||
}
|
||||
|
||||
$cooperative = Organization::where('hashkey', $coopHash)
|
||||
->where('type', 'COOPERATIVE')
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
|
||||
if (!$cooperative) {
|
||||
return response()->json(['success' => false, 'message' => 'Cooperative not found'], 404);
|
||||
}
|
||||
|
||||
$existing = CooperativeMember::where('organization_id', $cooperative->id)
|
||||
->where('user_id', $user->id)
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
return response()->json(['success' => false, 'message' => 'Already a member'], 409);
|
||||
}
|
||||
|
||||
$member = new CooperativeMember(array_merge(
|
||||
$request->only([
|
||||
'membership_type', 'membership_level', 'year_beginning',
|
||||
'officer_position', 'officer_level', 'concurrent_position', 'concurrent_level',
|
||||
'cooperative_position', 'cooperative_name_alt',
|
||||
'priority_sector', 'common_bond', 'vulnerability_classifications',
|
||||
'philsys_id', 'sss_number', 'pagibig_number',
|
||||
'slp_track', 'slp_association_name', 'listahanan_id', 'fourtps_household_id',
|
||||
'tupad_category', 'tupad_insurance_beneficiary_name', 'tupad_insurance_beneficiary_relation',
|
||||
'preferred_occupation', 'nsrp_skills', 'employment_status', 'program_participation',
|
||||
]),
|
||||
[
|
||||
'hashkey' => Str::random(64),
|
||||
'organization_id' => $cooperative->id,
|
||||
'user_id' => $user->id,
|
||||
'role' => 'MEMBER',
|
||||
'joined_at' => now(),
|
||||
'is_active' => true,
|
||||
'created_by' => $user->id,
|
||||
]
|
||||
));
|
||||
$member->save();
|
||||
|
||||
$settings = $user->settings ?? [];
|
||||
$settings['cooperatives'] = array_unique(array_merge($settings['cooperatives'] ?? [], [$coopHash]));
|
||||
$user->settings = $settings;
|
||||
$user->save();
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Membership application submitted successfully.']);
|
||||
}
|
||||
|
||||
public function publicGetCooperative(Request $request, string $hkey)
|
||||
{
|
||||
try {
|
||||
$cooperative = Organization::where('hashkey', $hkey)
|
||||
->where('type', 'COOPERATIVE')
|
||||
->where('is_active', true)
|
||||
->select(['id', 'hashkey', 'name', 'type', 'cooperative_type', 'cooperative_category', 'contact_person', 'contact_number', 'address'])
|
||||
->first();
|
||||
} catch (\Throwable $e) {
|
||||
return Response::json(['success' => false, 'message' => 'Service temporarily unavailable'], 500);
|
||||
}
|
||||
|
||||
if (!$cooperative) {
|
||||
return Response::json(['success' => false, 'message' => 'Cooperative not found'], 404);
|
||||
}
|
||||
|
||||
return Response::json(['success' => true, 'data' => $cooperative]);
|
||||
}
|
||||
|
||||
public function publicRegisterMember(Request $request)
|
||||
{
|
||||
$hkey = $request->input('cooperative_hash');
|
||||
if (!$hkey) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$cooperative = Organization::where('hashkey', $hkey)
|
||||
->where('type', 'COOPERATIVE')
|
||||
->where('is_active', true)
|
||||
->first();
|
||||
|
||||
if (!$cooperative) {
|
||||
return Response::json(['success' => false, 'message' => 'Cooperative not found'], 404);
|
||||
}
|
||||
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'username' => 'required|string|max:255|unique:users,username',
|
||||
'mobile_number' => ['required', 'string', 'max:20', 'unique:users,mobile_number', 'regex:/^(09|\+639)\d{9}$/'],
|
||||
'password' => 'required|string|min:6',
|
||||
]);
|
||||
} catch (\Hypervel\Validation\ValidationException $e) {
|
||||
return Response::json(['success' => false, 'errors' => $e->errors()], 422);
|
||||
}
|
||||
|
||||
$parentUser = User::where('id', $cooperative->created_by)->first()
|
||||
?? User::where('acct_type', 'COORDINATOR')->first()
|
||||
?? User::orderBy('id')->first();
|
||||
|
||||
if (!$parentUser) {
|
||||
return Response::json(['success' => false, 'message' => 'No valid parent user found'], 500);
|
||||
}
|
||||
|
||||
$user = new User();
|
||||
$user->username = $validated['username'];
|
||||
$user->name = $validated['name'];
|
||||
$user->mobile_number = $validated['mobile_number'];
|
||||
$user->password = Hash::make($validated['password']);
|
||||
$user->parentuid = $parentUser->id;
|
||||
$user->acct_type = 'user';
|
||||
$user->active = true;
|
||||
$user->save();
|
||||
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'user_hashkey' => $user->hashkey,
|
||||
'message' => 'Account created. Please complete your membership application.',
|
||||
], 201);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
257
app/Http/Controllers/Market/CooperativeDocumentController.php
Normal file
257
app/Http/Controllers/Market/CooperativeDocumentController.php
Normal file
@@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Market;
|
||||
|
||||
use App\Http\Controllers\FilesMainController;
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\Market\CooperativeDocument;
|
||||
use App\Models\Market\Organization;
|
||||
use App\Models\FileList;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Enums\UserActions;
|
||||
use Hypervel\Support\Str;
|
||||
|
||||
class CooperativeDocumentController
|
||||
{
|
||||
public function listDocuments(Request $request)
|
||||
{
|
||||
$orgHash = $request->input('orgHash');
|
||||
if (!$orgHash) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$org = Organization::where('hashkey', $orgHash)->first();
|
||||
if (!$org) {
|
||||
return ResponseHelper::returnError('Organization not found', 404);
|
||||
}
|
||||
|
||||
// Get latest versions (where parent_hashkey IS NULL or it is the latest in its group)
|
||||
// For simplicity, we'll fetch all active docs and group them by parent_hashkey or hashkey
|
||||
$allDocs = CooperativeDocument::where('organization_id', $org->id)
|
||||
->where('is_active', true)
|
||||
->orderBy('version_number', 'desc')
|
||||
->get();
|
||||
|
||||
$fileHashes = $allDocs->pluck('file_hashkey')->filter()->unique()->toArray();
|
||||
$fileLists = FileList::whereIn('hashkey', $fileHashes)->with('fileContent')->get()->keyBy('hashkey');
|
||||
|
||||
$grouped = [];
|
||||
foreach ($allDocs as $doc) {
|
||||
$rootKey = $doc->parent_hashkey ?? $doc->hashkey;
|
||||
if (!isset($grouped[$rootKey])) {
|
||||
$grouped[$rootKey] = [];
|
||||
}
|
||||
$grouped[$rootKey][] = $doc;
|
||||
}
|
||||
|
||||
$data = [];
|
||||
foreach ($grouped as $rootKey => $versions) {
|
||||
$latest = $versions[0]; // ordered desc by version_number
|
||||
$fileList = $fileLists[$latest->file_hashkey] ?? null;
|
||||
if ($fileList) {
|
||||
$history = [];
|
||||
foreach ($versions as $v) {
|
||||
$vFileList = $fileLists[$v->file_hashkey] ?? null;
|
||||
if ($vFileList) {
|
||||
$history[] = [
|
||||
'hashkey' => $v->hashkey,
|
||||
'version' => $v->version_number,
|
||||
'name' => $vFileList->filename,
|
||||
'date' => $v->created_at ? $v->created_at->format('Y-m-d H:i') : null,
|
||||
'note' => $v->revision_note,
|
||||
'url' => $vFileList->resolvedUrl()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$data[] = [
|
||||
'hashkey' => $latest->hashkey,
|
||||
'parent_hashkey' => $latest->parent_hashkey,
|
||||
'name' => $fileList->filename,
|
||||
'type' => $latest->document_type ?? 'Document',
|
||||
'date' => $latest->created_at ? $latest->created_at->toDateString() : null,
|
||||
'size' => $this->formatBytes($fileList->fileContent->size_in_bytes ?? 0),
|
||||
'url' => $fileList->resolvedUrl(),
|
||||
'version' => $latest->version_number,
|
||||
'note' => $latest->revision_note,
|
||||
'history' => $history
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
public function uploadDocument(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageOrganizations)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$orgHash = $request->input('orgHash');
|
||||
$docType = $request->input('type', 'OTHERS');
|
||||
|
||||
if (!$orgHash || !$request->hasFile('file')) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$org = Organization::where('hashkey', $orgHash)->first();
|
||||
if (!$org) {
|
||||
return ResponseHelper::returnError('Organization not found', 404);
|
||||
}
|
||||
|
||||
$file = $request->file('file');
|
||||
$filename = $file->getClientFilename();
|
||||
|
||||
$fileList = FilesMainController::uploadFileList(
|
||||
$file,
|
||||
$filename,
|
||||
$filename,
|
||||
'Cooperative Document for ' . $org->name,
|
||||
['type' => 'coop_document', 'org_id' => $org->id],
|
||||
'CooperativeDocuments',
|
||||
[],
|
||||
0,
|
||||
'cooperative_document',
|
||||
);
|
||||
|
||||
if (!$fileList || !isset($fileList->hashkey)) {
|
||||
return ResponseHelper::returnError('File upload failed');
|
||||
}
|
||||
|
||||
$doc = new CooperativeDocument([
|
||||
'hashkey' => (string) Str::uuid(),
|
||||
'organization_id' => $org->id,
|
||||
'file_hashkey' => $fileList->hashkey,
|
||||
'document_type' => $docType,
|
||||
'created_by' => Auth::id(),
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
if ($doc->save()) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Document uploaded successfully',
|
||||
'data' => [
|
||||
'hashkey' => $doc->hashkey,
|
||||
'name' => $filename,
|
||||
'url' => $fileList->resolvedUrl()
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
return ResponseHelper::returnError('Failed to save document record');
|
||||
}
|
||||
|
||||
public function reviseDocument(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageOrganizations)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$parentHash = $request->input('parentHash');
|
||||
$note = $request->input('note');
|
||||
|
||||
if (!$parentHash || !$request->hasFile('file')) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$parentDoc = CooperativeDocument::where('hashkey', $parentHash)->first();
|
||||
if (!$parentDoc) {
|
||||
return ResponseHelper::returnError('Original document not found', 404);
|
||||
}
|
||||
|
||||
// The real parent is either its own parent or it is the parent
|
||||
$rootHash = $parentDoc->parent_hashkey ?? $parentDoc->hashkey;
|
||||
|
||||
// Find highest version number
|
||||
$lastVersion = CooperativeDocument::where('hashkey', $rootHash)
|
||||
->orWhere('parent_hashkey', $rootHash)
|
||||
->max('version_number');
|
||||
|
||||
$file = $request->file('file');
|
||||
$filename = $file->getClientFilename();
|
||||
|
||||
$fileList = FilesMainController::uploadFileList(
|
||||
$file,
|
||||
$filename,
|
||||
$filename,
|
||||
'Revision of ' . $parentDoc->hashkey,
|
||||
['type' => 'coop_document_revision', 'parent_id' => $parentDoc->id],
|
||||
'CooperativeDocuments',
|
||||
[],
|
||||
0,
|
||||
'cooperative_document_revision',
|
||||
);
|
||||
|
||||
if (!$fileList) {
|
||||
return ResponseHelper::returnError('File upload failed');
|
||||
}
|
||||
|
||||
$doc = new CooperativeDocument([
|
||||
'hashkey' => (string) Str::uuid(),
|
||||
'parent_hashkey' => $rootHash,
|
||||
'version_number' => $lastVersion + 1,
|
||||
'organization_id' => $parentDoc->organization_id,
|
||||
'file_hashkey' => $fileList->hashkey,
|
||||
'document_type' => $parentDoc->document_type,
|
||||
'revision_note' => $note,
|
||||
'created_by' => Auth::id(),
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
if ($doc->save()) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Revision uploaded successfully',
|
||||
'data' => $doc
|
||||
]);
|
||||
}
|
||||
|
||||
return ResponseHelper::returnError('Failed to save revision record');
|
||||
}
|
||||
|
||||
public function deleteDocument(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageOrganizations)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$hashkey = $request->input('hashkey');
|
||||
if (!$hashkey) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$doc = CooperativeDocument::where('hashkey', $hashkey)->first();
|
||||
if (!$doc) {
|
||||
return ResponseHelper::returnError('Document not found', 404);
|
||||
}
|
||||
|
||||
// If it's a version, we might want to just deactivate that version.
|
||||
// If it's the root, we might want to deactivate all versions.
|
||||
// For now, let's just deactivate the specific one.
|
||||
$doc->is_active = false;
|
||||
if ($doc->save()) {
|
||||
return response()->json(['success' => true, 'message' => 'Document/Revision deleted']);
|
||||
}
|
||||
|
||||
return ResponseHelper::returnError('Failed to delete document');
|
||||
}
|
||||
|
||||
private function formatBytes($bytes, $precision = 1)
|
||||
{
|
||||
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
$bytes = max($bytes, 0);
|
||||
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
|
||||
$pow = min($pow, count($units) - 1);
|
||||
$bytes /= (1 << (10 * $pow));
|
||||
return round($bytes, $precision) . ' ' . $units[$pow];
|
||||
}
|
||||
}
|
||||
269
app/Http/Controllers/Market/CreditController.php
Normal file
269
app/Http/Controllers/Market/CreditController.php
Normal file
@@ -0,0 +1,269 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Market;
|
||||
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Http\Controllers\Helpers\PaymentProcessor;
|
||||
use App\Http\Controllers\Helpers\QrphDecoder;
|
||||
use App\Models\SystemSetting;
|
||||
use App\Models\Accounting\MemberLedger;
|
||||
use App\Models\User;
|
||||
use App\Models\GlobalTransaction;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\DB;
|
||||
use Hypervel\Support\Str;
|
||||
use App\Enums\Market\ProductTransactionType;
|
||||
use App\Enums\Market\TransactionFlow;
|
||||
|
||||
class CreditController
|
||||
{
|
||||
public function getWalletData(Request $request)
|
||||
{
|
||||
$user = User::find(Auth::id());
|
||||
if (!$user) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
$history = MemberLedger::where('user_id', $user->id)
|
||||
->where('is_active', true)
|
||||
->orderBy('created_at', 'desc')
|
||||
->limit(20)
|
||||
->get();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'balance' => $user->total_balance,
|
||||
'credit' => $user->total_credit,
|
||||
'history' => $history
|
||||
]);
|
||||
}
|
||||
|
||||
public function topUp(Request $request)
|
||||
{
|
||||
// Check if Top Up is enabled globally
|
||||
if (!(\App\Models\SystemSetting::getValue('top_up_enabled', true))) {
|
||||
return ResponseHelper::returnError('Credit top-up is currently disabled by administrators.');
|
||||
}
|
||||
|
||||
$amount = (float) $request->input('amount');
|
||||
$method = $request->input('method', 'GCASH');
|
||||
|
||||
if ($amount <= 0) {
|
||||
return ResponseHelper::returnError('Amount must be greater than zero');
|
||||
}
|
||||
|
||||
$user = User::find(Auth::id());
|
||||
if (!$user) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
// Start Transaction
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
// 1. Simulate Payment Success (in real life this would be a webhook/callback)
|
||||
$payment = PaymentProcessor::initiatePayment($amount, $method, $user->hashkey);
|
||||
|
||||
if (!$payment['success']) {
|
||||
throw new \Exception('Payment initiation failed');
|
||||
}
|
||||
|
||||
// 2. Update User Balance
|
||||
$user->total_balance += $amount;
|
||||
$user->save();
|
||||
|
||||
// 3. Record in MemberLedger
|
||||
$ledger = new MemberLedger([
|
||||
'hashkey' => Str::random(64),
|
||||
'user_id' => $user->id,
|
||||
'amount' => $amount,
|
||||
'transaction_type' => 'TOP_UP',
|
||||
'flow' => 'IN',
|
||||
'balance_after' => $user->total_balance,
|
||||
'description' => "Credit Top-up via {$method}",
|
||||
'reference_id' => $payment['transaction_id'],
|
||||
'created_by' => $user->id,
|
||||
'is_active' => true,
|
||||
]);
|
||||
$ledger->save();
|
||||
|
||||
// 4. Record in GlobalTransaction (for compatibility with existing reports)
|
||||
$globalTxn = new GlobalTransaction([
|
||||
'hashkey' => Str::random(64),
|
||||
'user_id' => $user->id,
|
||||
'amount' => $amount,
|
||||
'type' => ProductTransactionType::TOP_UP,
|
||||
'status' => 'COMPLETED',
|
||||
'description' => "Credit Top-up via {$method}",
|
||||
'flow' => TransactionFlow::INCOME,
|
||||
'created_by' => $user->id,
|
||||
]);
|
||||
$globalTxn->save();
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Top-up successful',
|
||||
'balance' => $user->total_balance,
|
||||
'transaction_id' => $payment['transaction_id']
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return ResponseHelper::returnError('Top-up failed: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function transferCredit(Request $request)
|
||||
{
|
||||
$recipientHash = $request->input('recipient_hash');
|
||||
$amount = (float) $request->input('amount');
|
||||
|
||||
if ($amount <= 0) return ResponseHelper::returnError('Invalid amount');
|
||||
|
||||
$sender = User::find(Auth::id());
|
||||
if (!$sender) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
if ($sender->total_balance < $amount) {
|
||||
return ResponseHelper::returnError('Insufficient balance');
|
||||
}
|
||||
|
||||
$recipient = User::where('hashkey', $recipientHash)->first();
|
||||
if (!$recipient) return ResponseHelper::returnError('Recipient not found');
|
||||
|
||||
if ($sender->id === $recipient->id) {
|
||||
return ResponseHelper::returnError('Cannot transfer to yourself');
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
// Deduct from sender
|
||||
$sender->total_balance -= $amount;
|
||||
$sender->save();
|
||||
|
||||
// Add to recipient
|
||||
$recipient->total_balance += $amount;
|
||||
$recipient->save();
|
||||
|
||||
$txnRef = Str::random(12);
|
||||
|
||||
// Record Ledger for Sender
|
||||
MemberLedger::create([
|
||||
'hashkey' => Str::random(64),
|
||||
'user_id' => $sender->id,
|
||||
'amount' => $amount,
|
||||
'transaction_type' => 'TRANSFER_OUT',
|
||||
'flow' => 'OUT',
|
||||
'balance_after' => $sender->total_balance,
|
||||
'description' => "Credit Transfer to {$recipient->fullname}",
|
||||
'reference_id' => $txnRef,
|
||||
'created_by' => $sender->id,
|
||||
]);
|
||||
|
||||
// Record Ledger for Recipient
|
||||
MemberLedger::create([
|
||||
'hashkey' => Str::random(64),
|
||||
'user_id' => $recipient->id,
|
||||
'amount' => $amount,
|
||||
'transaction_type' => 'TRANSFER_IN',
|
||||
'flow' => 'IN',
|
||||
'balance_after' => $recipient->total_balance,
|
||||
'description' => "Credit Transfer from {$sender->fullname}",
|
||||
'reference_id' => $txnRef,
|
||||
'created_by' => $sender->id,
|
||||
]);
|
||||
|
||||
DB::commit();
|
||||
return response()->json(['success' => true, 'message' => 'Transfer successful']);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
return ResponseHelper::returnError('Transfer failed');
|
||||
}
|
||||
}
|
||||
|
||||
public function searchUsers(Request $request)
|
||||
{
|
||||
$query = $request->input('q');
|
||||
if (empty($query)) return response()->json(['success' => true, 'data' => []]);
|
||||
|
||||
$users = User::where('fullname', 'like', "%{$query}%")
|
||||
->orWhere('name', 'like', "%{$query}%")
|
||||
->orWhere('mobile_number', 'like', "%{$query}%")
|
||||
->where('id', '!=', Auth::id())
|
||||
->limit(10)
|
||||
->get(['hashkey', 'fullname', 'name', 'mobile_number']);
|
||||
|
||||
return response()->json(['success' => true, 'data' => $users]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET the current QRPH payment code (available to all authenticated users for top-up display).
|
||||
*/
|
||||
public function getQrphCode(Request $request)
|
||||
{
|
||||
$raw = SystemSetting::getValue('qrph_payment_code', null);
|
||||
$imgHashkey = SystemSetting::getValue('qrph_payment_image_hashkey', null);
|
||||
|
||||
if (empty($raw)) {
|
||||
return response()->json(['success' => true, 'qrph' => null, 'decoded' => null, 'image_url' => null]);
|
||||
}
|
||||
|
||||
$decoded = QrphDecoder::decode($raw);
|
||||
$imageUrl = $imgHashkey ? "/RequestData/File/{$imgHashkey}" : null;
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'qrph' => $raw,
|
||||
'decoded' => $decoded,
|
||||
'image_url' => $imageUrl,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* SET the QRPH payment code — ULTIMATE only (enforced by route middleware).
|
||||
* Accepts optional image_hashkey from a prior /File/Upload/QrphPayment call.
|
||||
*/
|
||||
public function setQrphCode(Request $request)
|
||||
{
|
||||
$raw = trim($request->input('qrph_code', ''));
|
||||
$imgHashkey = trim($request->input('image_hashkey', ''));
|
||||
|
||||
if (empty($raw)) {
|
||||
SystemSetting::setValue('qrph_payment_code', '');
|
||||
SystemSetting::setValue('qrph_payment_image_hashkey', '');
|
||||
return response()->json(['success' => true, 'message' => 'QRPH code cleared.']);
|
||||
}
|
||||
|
||||
$decoded = QrphDecoder::decode($raw);
|
||||
|
||||
SystemSetting::setValue('qrph_payment_code', $raw);
|
||||
if (!empty($imgHashkey)) {
|
||||
SystemSetting::setValue('qrph_payment_image_hashkey', $imgHashkey);
|
||||
}
|
||||
|
||||
$imageUrl = $imgHashkey ? "/RequestData/File/{$imgHashkey}" : null;
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'QRPH code saved.',
|
||||
'decoded' => $decoded,
|
||||
'image_url' => $imageUrl,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a QRPH string without saving — for preview before saving.
|
||||
*/
|
||||
public function decodeQrph(Request $request)
|
||||
{
|
||||
$raw = trim($request->input('qrph_code', ''));
|
||||
if (empty($raw)) {
|
||||
return ResponseHelper::returnError('No QR string provided.');
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'decoded' => QrphDecoder::decode($raw),
|
||||
]);
|
||||
}
|
||||
}
|
||||
143
app/Http/Controllers/Market/FarmerController.php
Normal file
143
app/Http/Controllers/Market/FarmerController.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Market;
|
||||
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\Market\FarmerProfile;
|
||||
use App\Models\Market\Organization;
|
||||
use App\Models\User;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Enums\UserActions;
|
||||
|
||||
class FarmerController
|
||||
{
|
||||
public function registerFarmer(Request $request)
|
||||
{
|
||||
$currentUser = Auth::user();
|
||||
if (!$currentUser) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'user_hash' => 'nullable|string',
|
||||
'farm_name' => 'required|string|max:255',
|
||||
'farm_location' => 'nullable|string',
|
||||
'organization_hash' => 'nullable|string',
|
||||
'main_crops' => 'nullable|array',
|
||||
]);
|
||||
|
||||
// Determine the target user - if user_hash provided, use that user; otherwise use current user
|
||||
if (!empty($validated['user_hash'])) {
|
||||
$user = User::where('hashkey', $validated['user_hash'])->first();
|
||||
if (!$user) {
|
||||
return ResponseHelper::returnError('User not found', 404);
|
||||
}
|
||||
} else {
|
||||
$user = $currentUser;
|
||||
}
|
||||
|
||||
$organization = $validated['organization_hash'] ? Organization::where('hashkey', $validated['organization_hash'])->first() : null;
|
||||
|
||||
// Check if user already has a farmer profile
|
||||
$existingProfile = FarmerProfile::where('user_id', $user->id)->first();
|
||||
if ($existingProfile) {
|
||||
return ResponseHelper::returnError('User already has a farmer profile');
|
||||
}
|
||||
|
||||
$profile = new FarmerProfile([
|
||||
'user_id' => $user->id,
|
||||
'organization_id' => $organization?->id,
|
||||
'farm_name' => $validated['farm_name'],
|
||||
'farm_location' => $validated['farm_location'],
|
||||
'main_crops' => $validated['main_crops'],
|
||||
'verification_status' => 'VERIFIED',
|
||||
'created_by' => $currentUser->id,
|
||||
]);
|
||||
|
||||
if ($profile->save()) {
|
||||
return ResponseHelper::returnSuccessResponse($profile, $profile->hashkey, 'Farmer profile created and pending verification');
|
||||
}
|
||||
|
||||
return ResponseHelper::returnError('Failed to create farmer profile');
|
||||
}
|
||||
|
||||
public function listFarmers(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewFarmers)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$farmers = FarmerProfile::with(['user', 'organization'])->orderBy('created_at', 'desc')->get();
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $farmers
|
||||
]);
|
||||
}
|
||||
|
||||
public function verifyFarmer(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::VerifyFarmer)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$hashkey = $request->input('target');
|
||||
$status = $request->input('status'); // VERIFIED, REJECTED
|
||||
|
||||
if (!$hashkey || !$status) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$profile = FarmerProfile::where('hashkey', $hashkey)->first();
|
||||
if (!$profile) {
|
||||
return ResponseHelper::returnError('Profile not found', 404);
|
||||
}
|
||||
|
||||
$profile->verification_status = $status;
|
||||
$profile->save();
|
||||
|
||||
return ResponseHelper::returnSuccessResponse($profile, $profile->hashkey, 'Farmer verification status updated');
|
||||
}
|
||||
|
||||
public function listOrganizations()
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewOrganizations)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$orgs = Organization::where('is_active', true)->get();
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $orgs
|
||||
]);
|
||||
}
|
||||
|
||||
public function createOrganization(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::CreateOrganization)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'type' => 'required|string|in:COOPERATIVE,ASSOCIATION,COMPANY',
|
||||
'address' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$org = new Organization([
|
||||
'name' => $validated['name'],
|
||||
'type' => $validated['type'],
|
||||
'address' => $validated['address'],
|
||||
]);
|
||||
|
||||
if ($org->save()) {
|
||||
return ResponseHelper::returnSuccessResponse($org, $org->hashkey, 'Organization created');
|
||||
}
|
||||
|
||||
return ResponseHelper::returnError('Failed to create organization');
|
||||
}
|
||||
}
|
||||
178
app/Http/Controllers/Market/GovernanceController.php
Normal file
178
app/Http/Controllers/Market/GovernanceController.php
Normal file
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Market;
|
||||
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\Market\CooperativeResolution;
|
||||
use App\Models\Market\CooperativeVote;
|
||||
use App\Models\Market\Organization;
|
||||
use App\Models\Market\CooperativeMember;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Enums\UserActions;
|
||||
use Hypervel\Support\Str;
|
||||
|
||||
class GovernanceController
|
||||
{
|
||||
public function listResolutions(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewResolutions)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$orgHash = $request->input('org_hash');
|
||||
if (!$orgHash) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$org = Organization::where('hashkey', $orgHash)->first();
|
||||
if (!$org) {
|
||||
return ResponseHelper::returnError('Organization not found', 404);
|
||||
}
|
||||
|
||||
$resolutions = CooperativeResolution::where('organization_id', $org->id)
|
||||
->where('is_active', true)
|
||||
->withCount(['votes as yes_votes' => function($query) {
|
||||
$query->where('vote_cast', 'YES');
|
||||
}])
|
||||
->withCount(['votes as no_votes' => function($query) {
|
||||
$query->where('vote_cast', 'NO');
|
||||
}])
|
||||
->withCount(['votes as abstain_votes' => function($query) {
|
||||
$query->where('vote_cast', 'ABSTAIN');
|
||||
}])
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $resolutions
|
||||
]);
|
||||
}
|
||||
|
||||
public function createResolution(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::CreateResolution)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$orgHash = $request->input('org_hash');
|
||||
$title = $request->input('title');
|
||||
$description = $request->input('description');
|
||||
|
||||
if (!$orgHash || !$title) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$org = Organization::where('hashkey', $orgHash)->first();
|
||||
if (!$org) {
|
||||
return ResponseHelper::returnError('Organization not found', 404);
|
||||
}
|
||||
|
||||
$resolution = new CooperativeResolution([
|
||||
'hashkey' => Str::random(64),
|
||||
'organization_id' => $org->id,
|
||||
'title' => trim($title),
|
||||
'description' => trim($description ?? ''),
|
||||
'status' => 'PROPOSED',
|
||||
'created_by' => Auth::id(),
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
if ($resolution->save()) {
|
||||
return ResponseHelper::returnSuccessResponse($resolution, $resolution->hashkey, 'Resolution created successfully');
|
||||
}
|
||||
|
||||
return ResponseHelper::returnError('Failed to create resolution');
|
||||
}
|
||||
|
||||
public function castVote(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::VoteResolution)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$resolutionHash = $request->input('resolution_hash');
|
||||
$vote = $request->input('vote'); // YES, NO, ABSTAIN
|
||||
|
||||
if (!$resolutionHash || !in_array($vote, ['YES', 'NO', 'ABSTAIN'])) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$resolution = CooperativeResolution::where('hashkey', $resolutionHash)->first();
|
||||
if (!$resolution) {
|
||||
return ResponseHelper::returnError('Resolution not found', 404);
|
||||
}
|
||||
|
||||
// Check if user is a member of the organization
|
||||
$isMember = CooperativeMember::where('organization_id', $resolution->organization_id)
|
||||
->where('user_id', Auth::id())
|
||||
->where('is_active', true)
|
||||
->exists();
|
||||
|
||||
if (!$isMember) {
|
||||
return ResponseHelper::returnError('Only active members can vote on resolutions', 403);
|
||||
}
|
||||
|
||||
// Check for existing vote
|
||||
$existingVote = CooperativeVote::where('resolution_id', $resolution->id)
|
||||
->where('user_id', Auth::id())
|
||||
->first();
|
||||
|
||||
if ($existingVote) {
|
||||
$existingVote->vote_cast = $vote;
|
||||
$existingVote->updated_by = Auth::id();
|
||||
if ($existingVote->save()) {
|
||||
return ResponseHelper::returnSuccessResponse($existingVote, $existingVote->hashkey, 'Vote updated successfully');
|
||||
}
|
||||
} else {
|
||||
$newVote = new CooperativeVote([
|
||||
'hashkey' => Str::random(64),
|
||||
'resolution_id' => $resolution->id,
|
||||
'user_id' => Auth::id(),
|
||||
'vote_cast' => $vote,
|
||||
'created_by' => Auth::id(),
|
||||
'is_active' => true,
|
||||
]);
|
||||
if ($newVote->save()) {
|
||||
return ResponseHelper::returnSuccessResponse($newVote, $newVote->hashkey, 'Vote cast successfully');
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseHelper::returnError('Failed to record vote');
|
||||
}
|
||||
|
||||
public function updateResolutionStatus(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageOrganizations)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$resolutionHash = $request->input('resolution_hash');
|
||||
$status = $request->input('status'); // APPROVED, RESCINDED
|
||||
|
||||
if (!$resolutionHash || !in_array($status, ['APPROVED', 'RESCINDED', 'PROPOSED'])) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$resolution = CooperativeResolution::where('hashkey', $resolutionHash)->first();
|
||||
if (!$resolution) {
|
||||
return ResponseHelper::returnError('Resolution not found', 404);
|
||||
}
|
||||
|
||||
$resolution->status = $status;
|
||||
$resolution->updated_by = Auth::id();
|
||||
if ($status === 'APPROVED') {
|
||||
$resolution->date_approved = now();
|
||||
}
|
||||
|
||||
if ($resolution->save()) {
|
||||
return ResponseHelper::returnSuccessResponse($resolution, $resolution->hashkey, 'Resolution status updated');
|
||||
}
|
||||
|
||||
return ResponseHelper::returnError('Failed to update resolution status');
|
||||
}
|
||||
}
|
||||
472
app/Http/Controllers/Market/PerformanceController.php
Normal file
472
app/Http/Controllers/Market/PerformanceController.php
Normal file
@@ -0,0 +1,472 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Market;
|
||||
|
||||
use App\Enums\UserTypes;
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\Market\PosSession;
|
||||
use App\Models\Market\PosTransaction;
|
||||
use App\Models\Market\Product;
|
||||
use App\Models\Market\Store;
|
||||
use App\Models\User;
|
||||
use Hyperf\Stringable\Str;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\DB;
|
||||
use Hypervel\Support\Facades\Hash;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
|
||||
/**
|
||||
* Performance / load-testing endpoints.
|
||||
*
|
||||
* These bypass session auth so a client machine can hit them with curl.
|
||||
* Auth is via header `X-Perf-Token` matched against env `PERF_API_TOKEN`.
|
||||
* If `PERF_API_TOKEN` is unset the endpoints are disabled (403 for all).
|
||||
*
|
||||
* All endpoints return timing metrics (ms) so the caller can chart how the
|
||||
* box behaves under different batch sizes.
|
||||
*/
|
||||
class PerformanceController
|
||||
{
|
||||
private const DEFAULT_LIMIT = 1000;
|
||||
|
||||
private function authorize(Request $request)
|
||||
{
|
||||
$expected = (string) env('PERF_API_TOKEN', '');
|
||||
if ($expected === '') {
|
||||
return ResponseHelper::returnError('Performance API is disabled (PERF_API_TOKEN not set)', 403);
|
||||
}
|
||||
$provided = (string) ($request->header('X-Perf-Token') ?? $request->input('token', ''));
|
||||
if (!hash_equals($expected, $provided)) {
|
||||
return ResponseHelper::returnError('Invalid perf token', 401);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function actingUser(): ?User
|
||||
{
|
||||
$hash = (string) env('PERF_ACTOR_HASH', '');
|
||||
if ($hash !== '') {
|
||||
$u = User::where('hashkey', $hash)->first();
|
||||
if ($u) return $u;
|
||||
}
|
||||
return User::where('acct_type', UserTypes::ULTIMATE->value)
|
||||
->orderBy('id', 'asc')
|
||||
->first();
|
||||
}
|
||||
|
||||
private function clampCount($raw): int
|
||||
{
|
||||
$n = (int) $raw;
|
||||
if ($n < 1) $n = 1;
|
||||
if ($n > self::DEFAULT_LIMIT) $n = self::DEFAULT_LIMIT;
|
||||
return $n;
|
||||
}
|
||||
|
||||
private function ms(float $start): float
|
||||
{
|
||||
return round((microtime(true) - $start) * 1000, 3);
|
||||
}
|
||||
|
||||
public function ping(Request $request)
|
||||
{
|
||||
$deny = $this->authorize($request);
|
||||
if ($deny) return $deny;
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'ts' => now()->toIso8601String(),
|
||||
'php' => PHP_VERSION,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Core user seeder, returns ['hashes' => [...], 'ms' => float].
|
||||
*/
|
||||
private function _seedUsers(User $actor, int $count, string $type, ?int $parentId, string $prefix): array
|
||||
{
|
||||
$parentId = $parentId ?? $actor->id;
|
||||
$hashed = Hash::make('Perf12345!');
|
||||
$start = microtime(true);
|
||||
$created = [];
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
foreach (range(1, $count) as $i) {
|
||||
$suffix = Str::random(10);
|
||||
$u = User::create([
|
||||
'username' => "{$prefix}_{$suffix}",
|
||||
'name' => "Perf User {$i}",
|
||||
'mobile_number' => '09' . str_pad((string) random_int(0, 999999999), 9, '0', STR_PAD_LEFT),
|
||||
'password' => $hashed,
|
||||
'acct_type' => $type,
|
||||
'parentuid' => $parentId,
|
||||
'active' => true,
|
||||
]);
|
||||
$created[] = $u->hashkey;
|
||||
}
|
||||
DB::commit();
|
||||
} catch (\Throwable $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
return ['hashes' => $created, 'ms' => $this->ms($start)];
|
||||
}
|
||||
|
||||
private function _seedStores(User $actor, int $count, string $category, ?int $ownerId, string $prefix): array
|
||||
{
|
||||
$start = microtime(true);
|
||||
$created = [];
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
foreach (range(1, $count) as $i) {
|
||||
$storeCode = StoreController::generateStoreCode($category);
|
||||
$s = Store::create([
|
||||
'storecode' => $storeCode,
|
||||
'name' => "{$prefix} " . Str::random(8),
|
||||
'description' => "Synthetic store for perf testing #{$i}",
|
||||
'address' => 'Perf Lane ' . random_int(1, 9999),
|
||||
'category' => $category,
|
||||
'subcategory' => '',
|
||||
'owner_id' => $ownerId,
|
||||
'created_by' => $actor->id,
|
||||
'is_active' => true,
|
||||
'status' => 'active',
|
||||
]);
|
||||
$created[] = $s->hashkey;
|
||||
}
|
||||
DB::commit();
|
||||
} catch (\Throwable $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
return ['hashes' => $created, 'ms' => $this->ms($start)];
|
||||
}
|
||||
|
||||
private function _seedProducts(User $actor, int $count, ?Store $store, bool $attach, string $prefix): array
|
||||
{
|
||||
$start = microtime(true);
|
||||
$created = [];
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
foreach (range(1, $count) as $i) {
|
||||
$price = random_int(10, 5000);
|
||||
$available = random_int(10, 500);
|
||||
$p = Product::create([
|
||||
'name' => "{$prefix} " . Str::random(8),
|
||||
'description' => "Synthetic product for perf testing #{$i}",
|
||||
'price' => $price,
|
||||
'unitname' => 'pc',
|
||||
'available' => $available,
|
||||
'category' => 'Perf',
|
||||
'subcategory' => 'Synthetic',
|
||||
'created_by' => $actor->id,
|
||||
'is_active' => true,
|
||||
]);
|
||||
if ($store && $attach) {
|
||||
$store->products()->attach($p->id, [
|
||||
'available' => $available,
|
||||
'price' => $price,
|
||||
'description' => $p->description,
|
||||
'is_active' => true,
|
||||
]);
|
||||
}
|
||||
$created[] = $p->hashkey;
|
||||
}
|
||||
DB::commit();
|
||||
} catch (\Throwable $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
return ['hashes' => $created, 'ms' => $this->ms($start)];
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/perf/seed/users
|
||||
* body: { count, type?, parent_hash?, prefix? }
|
||||
*/
|
||||
public function seedUsers(Request $request)
|
||||
{
|
||||
$deny = $this->authorize($request);
|
||||
if ($deny) return $deny;
|
||||
$actor = $this->actingUser();
|
||||
if (!$actor) return ResponseHelper::returnError('No actor user available', 500);
|
||||
|
||||
$count = $this->clampCount($request->input('count', 10));
|
||||
$type = (string) $request->input('type', UserTypes::USER->value);
|
||||
$prefix = (string) $request->input('prefix', 'perf');
|
||||
if (!UserTypes::tryFrom($type)) {
|
||||
return ResponseHelper::returnError("Invalid user type '{$type}'", 422);
|
||||
}
|
||||
$parentId = null;
|
||||
if ($p = $request->input('parent_hash')) {
|
||||
$parent = User::where('hashkey', (string) $p)->first();
|
||||
if ($parent) $parentId = $parent->id;
|
||||
}
|
||||
try {
|
||||
$r = $this->_seedUsers($actor, $count, $type, $parentId, $prefix);
|
||||
} catch (\Throwable $e) {
|
||||
return ResponseHelper::returnError($e->getMessage());
|
||||
}
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'count' => count($r['hashes']),
|
||||
'total_ms' => $r['ms'],
|
||||
'avg_ms' => round($r['ms'] / max(1, count($r['hashes'])), 3),
|
||||
'sample' => array_slice($r['hashes'], 0, 5),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/perf/seed/stores
|
||||
* body: { count, owner_hash?, category?, prefix? }
|
||||
*/
|
||||
public function seedStores(Request $request)
|
||||
{
|
||||
$deny = $this->authorize($request);
|
||||
if ($deny) return $deny;
|
||||
$actor = $this->actingUser();
|
||||
if (!$actor) return ResponseHelper::returnError('No actor user available', 500);
|
||||
|
||||
$count = $this->clampCount($request->input('count', 10));
|
||||
$category = (string) $request->input('category', 'General');
|
||||
$prefix = (string) $request->input('prefix', 'PerfStore');
|
||||
$ownerId = null;
|
||||
if ($h = $request->input('owner_hash')) {
|
||||
$owner = User::where('hashkey', (string) $h)->first();
|
||||
if ($owner) $ownerId = $owner->id;
|
||||
}
|
||||
try {
|
||||
$r = $this->_seedStores($actor, $count, $category, $ownerId, $prefix);
|
||||
} catch (\Throwable $e) {
|
||||
return ResponseHelper::returnError($e->getMessage());
|
||||
}
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'count' => count($r['hashes']),
|
||||
'total_ms' => $r['ms'],
|
||||
'avg_ms' => round($r['ms'] / max(1, count($r['hashes'])), 3),
|
||||
'sample' => array_slice($r['hashes'], 0, 5),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/perf/seed/products
|
||||
* body: { count, target_store_hash?, prefix?, attach_to_store? }
|
||||
*/
|
||||
public function seedProducts(Request $request)
|
||||
{
|
||||
$deny = $this->authorize($request);
|
||||
if ($deny) return $deny;
|
||||
$actor = $this->actingUser();
|
||||
if (!$actor) return ResponseHelper::returnError('No actor user available', 500);
|
||||
|
||||
$count = $this->clampCount($request->input('count', 10));
|
||||
$prefix = (string) $request->input('prefix', 'PerfProduct');
|
||||
$attach = (bool) $request->input('attach_to_store', true);
|
||||
$store = null;
|
||||
if ($h = $request->input('target_store_hash')) {
|
||||
$store = Store::where('hashkey', (string) $h)->first();
|
||||
if (!$store) return ResponseHelper::returnError('Target store not found', 404);
|
||||
}
|
||||
try {
|
||||
$r = $this->_seedProducts($actor, $count, $store, $attach, $prefix);
|
||||
} catch (\Throwable $e) {
|
||||
return ResponseHelper::returnError($e->getMessage());
|
||||
}
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'count' => count($r['hashes']),
|
||||
'total_ms' => $r['ms'],
|
||||
'avg_ms' => round($r['ms'] / max(1, count($r['hashes'])), 3),
|
||||
'attached_to_store' => $store?->hashkey,
|
||||
'sample' => array_slice($r['hashes'], 0, 5),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/perf/seed/batch
|
||||
* body: { users?, stores?, products?, prefix?, type?, category? }
|
||||
* Runs all three seeders sequentially with per-phase timings.
|
||||
*/
|
||||
public function seedBatch(Request $request)
|
||||
{
|
||||
$deny = $this->authorize($request);
|
||||
if ($deny) return $deny;
|
||||
$actor = $this->actingUser();
|
||||
if (!$actor) return ResponseHelper::returnError('No actor user available', 500);
|
||||
|
||||
$prefix = (string) $request->input('prefix', 'perf');
|
||||
$type = (string) $request->input('type', UserTypes::USER->value);
|
||||
if (!UserTypes::tryFrom($type)) {
|
||||
return ResponseHelper::returnError("Invalid user type '{$type}'", 422);
|
||||
}
|
||||
$category = (string) $request->input('category', 'General');
|
||||
|
||||
$users = max(0, (int) $request->input('users', 0));
|
||||
$stores = max(0, (int) $request->input('stores', 0));
|
||||
$products = max(0, (int) $request->input('products', 0));
|
||||
|
||||
$phases = [];
|
||||
$totalStart = microtime(true);
|
||||
|
||||
try {
|
||||
if ($users > 0) {
|
||||
$r = $this->_seedUsers($actor, $this->clampCount($users), $type, null, $prefix);
|
||||
$phases['users'] = ['count' => count($r['hashes']), 'ms' => $r['ms']];
|
||||
}
|
||||
if ($stores > 0) {
|
||||
$r = $this->_seedStores($actor, $this->clampCount($stores), $category, null, $prefix . 'Store');
|
||||
$phases['stores'] = ['count' => count($r['hashes']), 'ms' => $r['ms']];
|
||||
}
|
||||
if ($products > 0) {
|
||||
$r = $this->_seedProducts($actor, $this->clampCount($products), null, false, $prefix . 'Product');
|
||||
$phases['products'] = ['count' => count($r['hashes']), 'ms' => $r['ms']];
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return ResponseHelper::returnError($e->getMessage());
|
||||
}
|
||||
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'total_ms' => $this->ms($totalStart),
|
||||
'phases' => $phases,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/perf/pos/simulate
|
||||
* body: { store_hash, items?, cycles?, complete? }
|
||||
*
|
||||
* Runs end-to-end POS cycles entirely server-side: open session, add N
|
||||
* line items, optionally complete + archive. Returns per-phase timings.
|
||||
*/
|
||||
public function simulatePos(Request $request)
|
||||
{
|
||||
$deny = $this->authorize($request);
|
||||
if ($deny) return $deny;
|
||||
|
||||
$actor = $this->actingUser();
|
||||
if (!$actor) return ResponseHelper::returnError('No actor user available', 500);
|
||||
|
||||
$storeHash = (string) $request->input('store_hash', '');
|
||||
if ($storeHash === '') return ResponseHelper::returnError('store_hash is required', 422);
|
||||
|
||||
$store = Store::where('hashkey', $storeHash)->first();
|
||||
if (!$store) return ResponseHelper::returnError('Store not found', 404);
|
||||
|
||||
$items = max(1, min(200, (int) $request->input('items', 5)));
|
||||
$cycles = max(1, min(100, (int) $request->input('cycles', 1)));
|
||||
$complete = (bool) $request->input('complete', true);
|
||||
|
||||
// Pull a pool of products attached to this store; fall back to global
|
||||
// active products if the store has none yet.
|
||||
$productIds = DB::table('prd_str')
|
||||
->where('store_id', $store->id)
|
||||
->where('is_active', true)
|
||||
->pluck('product_id')
|
||||
->toArray();
|
||||
|
||||
if (empty($productIds)) {
|
||||
$productIds = Product::where('is_active', true)
|
||||
->limit(max(50, $items * 2))
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
if (empty($productIds)) {
|
||||
return ResponseHelper::returnError('No products available to simulate sales', 422);
|
||||
}
|
||||
|
||||
$products = Product::whereIn('id', $productIds)->get(['id', 'hashkey', 'price'])->keyBy('id');
|
||||
|
||||
$cycleResults = [];
|
||||
$totalStart = microtime(true);
|
||||
|
||||
for ($c = 1; $c <= $cycles; $c++) {
|
||||
$cStart = microtime(true);
|
||||
|
||||
// 1. Open session
|
||||
$t = microtime(true);
|
||||
$session = PosSession::create([
|
||||
'access_key' => Str::random(32),
|
||||
'store_id' => $store->id,
|
||||
'customer_name' => 'Perf Customer ' . $c,
|
||||
'status' => 'active',
|
||||
'created_by' => $actor->id,
|
||||
]);
|
||||
$openMs = $this->ms($t);
|
||||
|
||||
// 2. Add items (raw inserts for speed, mirrors PosController::addItem)
|
||||
$t = microtime(true);
|
||||
$now = now();
|
||||
$rows = [];
|
||||
$total = 0;
|
||||
for ($i = 0; $i < $items; $i++) {
|
||||
$pid = $productIds[array_rand($productIds)];
|
||||
$product = $products[$pid] ?? null;
|
||||
if (!$product) continue;
|
||||
$qty = random_int(1, 5);
|
||||
$price = (int) $product->price;
|
||||
$line = $price * $qty;
|
||||
$total += $line;
|
||||
$rows[] = [
|
||||
'pos_session_id' => $session->id,
|
||||
'product_id' => $pid,
|
||||
'quantity' => $qty,
|
||||
'price_at_sale' => $price,
|
||||
'total_price' => $line,
|
||||
'hashkey' => Str::uuid()->toString() . Str::random(100),
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
'created_by' => $actor->id,
|
||||
];
|
||||
}
|
||||
if (!empty($rows)) {
|
||||
DB::table('pos_transactions')->insert($rows);
|
||||
}
|
||||
$addMs = $this->ms($t);
|
||||
|
||||
// 3. Optionally complete the session
|
||||
$completeMs = null;
|
||||
if ($complete) {
|
||||
$t = microtime(true);
|
||||
DB::table('pos_sessions')->where('id', $session->id)->update([
|
||||
'total_amount' => $total,
|
||||
'received_amount' => $total,
|
||||
'change_amount' => 0,
|
||||
'status' => 'completed',
|
||||
'payment_method' => 'cash',
|
||||
'updated_at' => $now,
|
||||
'updated_by' => $actor->id,
|
||||
]);
|
||||
$completeMs = $this->ms($t);
|
||||
}
|
||||
|
||||
$cycleResults[] = [
|
||||
'session_hash' => $session->hashkey,
|
||||
'items' => count($rows),
|
||||
'total' => $total,
|
||||
'open_ms' => $openMs,
|
||||
'add_items_ms' => $addMs,
|
||||
'complete_ms' => $completeMs,
|
||||
'cycle_ms' => $this->ms($cStart),
|
||||
];
|
||||
}
|
||||
|
||||
$totalMs = $this->ms($totalStart);
|
||||
$cycleMsValues = array_column($cycleResults, 'cycle_ms');
|
||||
$avg = $cycleMsValues ? array_sum($cycleMsValues) / count($cycleMsValues) : 0.0;
|
||||
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'store_hash' => $store->hashkey,
|
||||
'cycles' => $cycles,
|
||||
'items_per_cycle' => $items,
|
||||
'total_ms' => $totalMs,
|
||||
'avg_cycle_ms' => round($avg, 3),
|
||||
'min_cycle_ms' => $cycleMsValues ? min($cycleMsValues) : 0,
|
||||
'max_cycle_ms' => $cycleMsValues ? max($cycleMsValues) : 0,
|
||||
'detail' => $cycleResults,
|
||||
]);
|
||||
}
|
||||
}
|
||||
774
app/Http/Controllers/Market/PosController.php
Normal file
774
app/Http/Controllers/Market/PosController.php
Normal file
@@ -0,0 +1,774 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Market;
|
||||
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\Market\PosSession;
|
||||
use App\Models\Market\PosTransaction;
|
||||
use App\Models\Market\PosSessionArchive;
|
||||
use App\Models\Market\Product;
|
||||
use App\Models\Market\Store;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use Hyperf\Stringable\Str;
|
||||
use App\Models\Market\PosAccessKey;
|
||||
use App\Models\Market\Customer;
|
||||
use Hypervel\Support\Facades\DB;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Enums\UserActions;
|
||||
use Hyperf\Coroutine\Coroutine;
|
||||
use App\Http\Controllers\Helpers\CacheHelper;
|
||||
|
||||
class PosController
|
||||
{
|
||||
public function startSession(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'store_hash' => 'nullable|string',
|
||||
'customer_name' => 'nullable|string|max:255',
|
||||
'access_key' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$store = null;
|
||||
$accessKeyObj = null;
|
||||
|
||||
if ($request->input('access_key')) {
|
||||
$accessKeyObj = PosAccessKey::where('access_key', $request->input('access_key'))
|
||||
->where('status', 'active')
|
||||
->first();
|
||||
|
||||
if ($accessKeyObj) {
|
||||
|
||||
if ($accessKeyObj->isExpired()) {
|
||||
$accessKeyObj->status = 'inactive';
|
||||
$accessKeyObj->save();
|
||||
return ResponseHelper::returnError('Access key has expired', 401);
|
||||
}
|
||||
|
||||
$store = $accessKeyObj->store;
|
||||
} elseif (!Auth::check()) {
|
||||
|
||||
return ResponseHelper::returnError('Invalid or inactive access key', 401);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$store && !empty($validated['store_hash'])) {
|
||||
$store = Store::where('hashkey', $validated['store_hash'])->first();
|
||||
}
|
||||
|
||||
if (!$store) {
|
||||
return ResponseHelper::returnError('No store found. Please open the POS from a store page or use a valid access key.', 422);
|
||||
}
|
||||
|
||||
// Authorization check
|
||||
if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $store)) {
|
||||
return ResponseHelper::returnError('You are not authorized to start a POS session for this store.', 403);
|
||||
}
|
||||
|
||||
/** @var PosSession $session */
|
||||
$session = PosSession::create([
|
||||
'access_key' => $accessKeyObj ? $accessKeyObj->access_key : Str::random(32),
|
||||
'store_id' => $store->id,
|
||||
'customer_name' => $validated['customer_name'] ?? null,
|
||||
'status' => 'active',
|
||||
'created_by' => Auth::id(),
|
||||
]);
|
||||
|
||||
if ($accessKeyObj) {
|
||||
$accessKeyObj->last_used_at = now();
|
||||
$accessKeyObj->save();
|
||||
}
|
||||
|
||||
$this->archiveSession($session, 'Session initialized');
|
||||
$this->invalidateSessionCache($session);
|
||||
|
||||
return ResponseHelper::returnSuccessResponse([
|
||||
'hashkey' => $session->hashkey,
|
||||
'access_key' => $session->access_key,
|
||||
], $session->hashkey, 'POS Session started');
|
||||
}
|
||||
|
||||
public function getSession(Request $request)
|
||||
{
|
||||
$hashkey = ResponseHelper::getTargetHash();
|
||||
$accessKey = $request->input('access_key');
|
||||
|
||||
if (!$hashkey && !$accessKey) {
|
||||
return ResponseHelper::returnError('No key provided', 400);
|
||||
}
|
||||
|
||||
$session = null;
|
||||
|
||||
if ($hashkey) {
|
||||
$q = $this->getBaseSessionQuery()->where('hashkey', $hashkey);
|
||||
$session = CacheHelper::get_cache($q);
|
||||
|
||||
if (!$session) {
|
||||
$session = $q->first();
|
||||
if ($session) {
|
||||
CacheHelper::set_cache($q, $session);
|
||||
}
|
||||
}
|
||||
|
||||
// If not a session hash, check if it's a store hash
|
||||
if (!$session) {
|
||||
$sq = Store::where('hashkey', $hashkey);
|
||||
$store = CacheHelper::get_cache($sq);
|
||||
if (!$store) {
|
||||
$store = $sq->first();
|
||||
if ($store) {
|
||||
CacheHelper::set_cache($sq, $store);
|
||||
}
|
||||
}
|
||||
|
||||
if ($store) {
|
||||
$q = $this->getBaseSessionQuery()
|
||||
->where('store_id', $store->id)
|
||||
->where('status', 'active')
|
||||
->orderBy('id', 'desc');
|
||||
|
||||
$session = CacheHelper::get_cache($q);
|
||||
if (!$session) {
|
||||
$session = $q->first();
|
||||
if ($session) {
|
||||
CacheHelper::set_cache($q, $session);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If still no session and we have an accessKey, try that (as fallback or primary if no hashkey)
|
||||
if (!$session && $accessKey) {
|
||||
$q = $this->getBaseSessionQuery()
|
||||
->where('access_key', $accessKey)
|
||||
->orderBy('id', 'desc');
|
||||
|
||||
$session = CacheHelper::get_cache($q);
|
||||
if (!$session) {
|
||||
$session = $q->first();
|
||||
if ($session) {
|
||||
CacheHelper::set_cache($q, $session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$session) {
|
||||
return ResponseHelper::returnError('Session not found', 404);
|
||||
}
|
||||
|
||||
// Authorization check
|
||||
if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $session->store_id)) {
|
||||
return ResponseHelper::returnError('You are not authorized to access this POS session.', 403);
|
||||
}
|
||||
|
||||
// Return the full session with all eager loaded relations
|
||||
return ResponseHelper::returnSuccessResponse($session, $session->hashkey);
|
||||
}
|
||||
|
||||
private function getBaseSessionQuery()
|
||||
{
|
||||
return PosSession::with([
|
||||
'transactions.product' => function ($q) {
|
||||
// Only fetch minimal columns needed for the POS to reduce serialization time
|
||||
$q->select(['id', 'hashkey', 'name', 'price', 'photourl', 'unitname', 'category']);
|
||||
},
|
||||
'store'
|
||||
]);
|
||||
}
|
||||
|
||||
public function addItem(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'session_hash' => 'required|string',
|
||||
'product_hash' => 'required|string',
|
||||
'quantity' => 'required|integer|min:1',
|
||||
'price' => 'nullable|numeric|min:0',
|
||||
]);
|
||||
|
||||
$sq = PosSession::where('hashkey', $validated['session_hash']);
|
||||
$session = CacheHelper::get_cache($sq);
|
||||
if (!$session) {
|
||||
$session = $sq->first();
|
||||
if ($session) {
|
||||
CacheHelper::set_cache($sq, $session);
|
||||
}
|
||||
}
|
||||
|
||||
$pq = Product::select(['id', 'hashkey', 'price', 'is_active', 'name'])->where('hashkey', $validated['product_hash']);
|
||||
$product = CacheHelper::get_cache($pq);
|
||||
if (!$product) {
|
||||
$product = $pq->first();
|
||||
if ($product) {
|
||||
CacheHelper::set_cache($pq, $product);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$session || !$product) {
|
||||
return ResponseHelper::returnError('Session or Product not found', 404);
|
||||
}
|
||||
|
||||
$sessionNeedsSave = false;
|
||||
if ($session->status !== 'active') {
|
||||
$session->status = 'active';
|
||||
$sessionNeedsSave = true;
|
||||
}
|
||||
|
||||
$price = (int) $product->price;
|
||||
$isActive = $product->is_active;
|
||||
|
||||
if ($session->store_id) {
|
||||
$psq = DB::table('prd_str')
|
||||
->where('store_id', $session->store_id)
|
||||
->where('product_id', $product->id)
|
||||
->select('price', 'is_active');
|
||||
|
||||
$storeProduct = CacheHelper::get_cache($psq);
|
||||
if (!$storeProduct) {
|
||||
$storeProduct = $psq->first();
|
||||
if ($storeProduct) {
|
||||
CacheHelper::set_cache($psq, $storeProduct, [], 3600); // 1 hour
|
||||
}
|
||||
}
|
||||
|
||||
if ($storeProduct) {
|
||||
if (isset($storeProduct->price)) {
|
||||
$price = (int) $storeProduct->price;
|
||||
}
|
||||
if (isset($storeProduct->is_active)) {
|
||||
$isActive = (bool) $storeProduct->is_active;
|
||||
}
|
||||
} else {
|
||||
return ResponseHelper::returnError('Product not available in this store', 403);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isActive) {
|
||||
return ResponseHelper::returnError('Product is currently inactive in this store', 403);
|
||||
}
|
||||
|
||||
// Use custom price if provided, otherwise use calculated store/product price
|
||||
if ($request->has('price')) {
|
||||
$price = (int) $validated['price'];
|
||||
}
|
||||
|
||||
// Update or create the transaction using raw DB for max speed
|
||||
$existingTx = DB::table('pos_transactions')
|
||||
->where('pos_session_id', $session->id)
|
||||
->where('product_id', $product->id)
|
||||
->first();
|
||||
|
||||
if ($existingTx) {
|
||||
DB::table('pos_transactions')->where('id', $existingTx->id)->update([
|
||||
'quantity' => $validated['quantity'],
|
||||
'price_at_sale' => $price,
|
||||
'total_price' => $price * $validated['quantity'],
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
} else {
|
||||
DB::table('pos_transactions')->insert([
|
||||
'pos_session_id' => $session->id,
|
||||
'product_id' => $product->id,
|
||||
'quantity' => $validated['quantity'],
|
||||
'price_at_sale' => $price,
|
||||
'total_price' => $price * $validated['quantity'],
|
||||
'hashkey' => \Hyperf\Stringable\Str::uuid()->toString() . \Hyperf\Stringable\Str::random(100),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
'created_by' => Auth::id() ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
// Load specific columns to be fast, just like in getSession to reduce payload and memory
|
||||
$session->load([
|
||||
'transactions.product' => function ($q) {
|
||||
$q->select(['id', 'hashkey', 'name', 'price', 'photourl', 'unitname', 'category']);
|
||||
},
|
||||
'store'
|
||||
]);
|
||||
$t_load = microtime(true);
|
||||
|
||||
// Update session totals in memory
|
||||
$total = $session->transactions->where('is_void', false)->sum('total_price');
|
||||
|
||||
$updateData = [];
|
||||
if ($session->total_amount !== (int) $total) {
|
||||
$session->total_amount = (int) $total;
|
||||
$updateData['total_amount'] = (int) $total;
|
||||
}
|
||||
if ($sessionNeedsSave) {
|
||||
$updateData['status'] = $session->status;
|
||||
}
|
||||
|
||||
// Use raw DB update to skip ModelSavingListener overhead while making sure we still record who updated it
|
||||
if (!empty($updateData)) {
|
||||
$updateData['updated_at'] = now();
|
||||
$updateData['updated_by'] = Auth::id();
|
||||
DB::table('pos_sessions')->where('id', $session->id)->update($updateData);
|
||||
}
|
||||
$t_db = microtime(true);
|
||||
|
||||
// Invalidate all possible session cache keys
|
||||
$this->invalidateSessionCache($session);
|
||||
|
||||
// Archive the session using already loaded transaction data (deferred to background coroutine)
|
||||
$this->archiveSession($session, 'Item added/updated: ' . $product->name, $session->transactions);
|
||||
|
||||
return ResponseHelper::returnSuccessResponse($session, $session->hashkey, 'Item added to session');
|
||||
}
|
||||
|
||||
public function removeItem(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'session_hash' => 'required|string',
|
||||
'transaction_id' => 'required|integer',
|
||||
]);
|
||||
|
||||
$session = PosSession::where('hashkey', $validated['session_hash'])->first();
|
||||
if (!$session) {
|
||||
return ResponseHelper::returnError('Session not found', 404);
|
||||
}
|
||||
|
||||
$transaction = PosTransaction::where('id', $validated['transaction_id'])
|
||||
->where('pos_session_id', $session->id)
|
||||
->first();
|
||||
|
||||
if ($transaction) {
|
||||
$transaction->delete();
|
||||
|
||||
// Re-calculate and archive efficiently
|
||||
// Load relations ONCE with only necessary columns
|
||||
$session->load([
|
||||
'transactions.product' => function ($q) {
|
||||
$q->select(['id', 'hashkey', 'name', 'price', 'photourl', 'unitname', 'category']);
|
||||
},
|
||||
'store'
|
||||
]);
|
||||
|
||||
$total = $session->transactions->where('is_void', false)->sum('total_price');
|
||||
$session->total_amount = (int) $total;
|
||||
DB::table('pos_sessions')->where('id', $session->id)->update([
|
||||
'total_amount' => (int) $total,
|
||||
'updated_at' => now(),
|
||||
'updated_by' => Auth::id(),
|
||||
]);
|
||||
|
||||
// Invalidate all possible session cache keys
|
||||
$this->invalidateSessionCache($session);
|
||||
|
||||
$this->archiveSession($session, 'Item removed', $session->transactions);
|
||||
} else {
|
||||
$session->load([
|
||||
'transactions.product' => function ($q) {
|
||||
$q->select(['id', 'hashkey', 'name', 'price', 'photourl', 'unitname', 'category']);
|
||||
},
|
||||
'store'
|
||||
]);
|
||||
}
|
||||
|
||||
return ResponseHelper::returnSuccessResponse($session, $session->hashkey, 'Item removed from session');
|
||||
}
|
||||
|
||||
public function completeSession(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'session_hash' => 'required|string',
|
||||
'received_amount' => 'required|integer|min:0',
|
||||
'payment_method' => 'required|string',
|
||||
'payment_field' => 'nullable|string',
|
||||
'customer_name' => 'nullable|string|max:255',
|
||||
]);
|
||||
|
||||
$session = PosSession::where('hashkey', $validated['session_hash'])->first();
|
||||
if (!$session) {
|
||||
return ResponseHelper::returnError('Session not found', 404);
|
||||
}
|
||||
|
||||
$session->received_amount = $validated['received_amount'];
|
||||
$session->change_amount = $validated['received_amount'] - $session->total_amount;
|
||||
$session->payment_method = $validated['payment_method'];
|
||||
$session->payment_details = ['payment_field' => $validated['payment_field']];
|
||||
if (!empty($validated['customer_name'])) {
|
||||
$session->customer_name = $validated['customer_name'];
|
||||
}
|
||||
$session->status = 'completed';
|
||||
$session->save();
|
||||
|
||||
// Invalidate cache
|
||||
$this->invalidateSessionCache($session);
|
||||
|
||||
if (!empty($validated['customer_name'])) {
|
||||
$customerName = trim($validated['customer_name']);
|
||||
$customer = Customer::where('name', $customerName)
|
||||
->where(function ($q) use ($session) {
|
||||
$q->where('store_id', $session->store_id)
|
||||
->orWhereNull('store_id');
|
||||
})
|
||||
->first();
|
||||
|
||||
if (!$customer) {
|
||||
Customer::create([
|
||||
'name' => $customerName,
|
||||
'store_id' => $session->store_id,
|
||||
'created_by' => Auth::id(),
|
||||
]);
|
||||
} else {
|
||||
$customer->updated_at = now();
|
||||
$customer->save();
|
||||
}
|
||||
}
|
||||
|
||||
$this->archiveSession($session, 'Session completed');
|
||||
|
||||
return ResponseHelper::returnSuccessResponse($session, $session->hashkey, 'Transaction completed');
|
||||
}
|
||||
|
||||
public function syncOffline(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'transactions' => 'required|array',
|
||||
'transactions.*.store_hash' => 'required|string',
|
||||
'transactions.*.customer_name' => 'nullable|string',
|
||||
'transactions.*.items' => 'required|array',
|
||||
'transactions.*.total' => 'required|numeric',
|
||||
'transactions.*.received' => 'required|numeric',
|
||||
'transactions.*.method' => 'required|string',
|
||||
'transactions.*.timestamp' => 'required|string',
|
||||
'transactions.*.local_id' => 'nullable|integer',
|
||||
]);
|
||||
|
||||
$syncedCount = 0;
|
||||
$syncedIds = [];
|
||||
$errors = [];
|
||||
$affectedStoreIds = [];
|
||||
|
||||
foreach ($validated['transactions'] as $txn) {
|
||||
try {
|
||||
DB::beginTransaction();
|
||||
|
||||
$store = Store::where('hashkey', $txn['store_hash'])->first();
|
||||
if (!$store) {
|
||||
throw new \Exception('Store not found for hash: ' . $txn['store_hash']);
|
||||
}
|
||||
|
||||
// Convert ISO 8601 timestamp to MySQL datetime format
|
||||
$offlineTimestamp = date('Y-m-d H:i:s', strtotime($txn['timestamp']));
|
||||
|
||||
// Create the session
|
||||
$session = new PosSession([
|
||||
'store_id' => $store->id,
|
||||
'customer_name' => $txn['customer_name'] ?? null,
|
||||
'total_amount' => (int) $txn['total'],
|
||||
'received_amount' => (int) $txn['received'],
|
||||
'change_amount' => (int) ($txn['received'] - $txn['total']),
|
||||
'payment_method' => $txn['method'],
|
||||
'status' => 'completed',
|
||||
'created_by' => Auth::id(),
|
||||
'access_key' => 'synced-' . Str::random(32),
|
||||
'hashkey' => Str::random(32) . '-' . Str::random(100),
|
||||
]);
|
||||
|
||||
// Manually set timestamps to preserve offline time
|
||||
$session->created_at = $offlineTimestamp;
|
||||
$session->updated_at = $offlineTimestamp;
|
||||
$session->save();
|
||||
|
||||
// Add Items
|
||||
foreach ($txn['items'] as $item) {
|
||||
$product = Product::where('hashkey', $item['product_hashkey'])->first();
|
||||
if (!$product) continue;
|
||||
|
||||
DB::table('pos_transactions')->insert([
|
||||
'pos_session_id' => $session->id,
|
||||
'product_id' => $product->id,
|
||||
'quantity' => $item['quantity'],
|
||||
'price_at_sale' => (int) $item['price_at_sale'],
|
||||
'total_price' => (int) ($item['price_at_sale'] * $item['quantity']),
|
||||
'hashkey' => Str::uuid()->toString() . Str::random(100),
|
||||
'created_at' => $offlineTimestamp,
|
||||
'updated_at' => now(),
|
||||
'created_by' => Auth::id(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Handle Customer
|
||||
if (!empty($txn['customer_name'])) {
|
||||
$customerName = trim($txn['customer_name']);
|
||||
$customer = Customer::where('name', $customerName)
|
||||
->where(function ($q) use ($store) {
|
||||
$q->where('store_id', $store->id)
|
||||
->orWhereNull('store_id');
|
||||
})
|
||||
->first();
|
||||
|
||||
if (!$customer) {
|
||||
Customer::create([
|
||||
'name' => $customerName,
|
||||
'store_id' => $store->id,
|
||||
'created_by' => Auth::id(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->archiveSession($session, 'Offline synced transaction');
|
||||
$this->invalidateSessionCache($session);
|
||||
|
||||
DB::commit();
|
||||
$syncedCount++;
|
||||
|
||||
if (isset($txn['local_id'])) {
|
||||
$syncedIds[] = $txn['local_id'];
|
||||
}
|
||||
|
||||
if (!in_array($store->id, $affectedStoreIds)) {
|
||||
$affectedStoreIds[] = $store->id;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
$errors[] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
return ResponseHelper::returnSuccessResponse([
|
||||
'synced_count' => $syncedCount,
|
||||
'synced_ids' => $syncedIds,
|
||||
'errors' => $errors
|
||||
], 'sync_offline', "Synced $syncedCount transactions");
|
||||
}
|
||||
|
||||
public function voidSession(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'session_hash' => 'required|string',
|
||||
'remarks' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$session = PosSession::where('hashkey', $validated['session_hash'])->first();
|
||||
if (!$session) {
|
||||
return ResponseHelper::returnError('Session not found', 404);
|
||||
}
|
||||
|
||||
if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $session->store_id)) {
|
||||
return ResponseHelper::returnError('You are not authorized to void this POS session.', 403);
|
||||
}
|
||||
|
||||
$session->status = 'voided';
|
||||
$session->is_void = true;
|
||||
$session->save();
|
||||
|
||||
// Invalidate cache
|
||||
$this->invalidateSessionCache($session);
|
||||
|
||||
$this->archiveSession($session, 'Session voided: ' . ($validated['remarks'] ?? 'No remarks'));
|
||||
|
||||
return ResponseHelper::returnSuccessResponse($session, $session->hashkey, 'Transaction voided');
|
||||
}
|
||||
|
||||
public function getPosSessions(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'store_hash' => 'required|string',
|
||||
'page' => 'nullable|integer|min:1',
|
||||
'per_page' => 'nullable|integer|min:1|max:100',
|
||||
]);
|
||||
|
||||
$store = Store::where('hashkey', $validated['store_hash'])->first();
|
||||
if (!$store) {
|
||||
return ResponseHelper::returnError('Store not found', 404);
|
||||
}
|
||||
|
||||
// Authorization check
|
||||
if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $store)) {
|
||||
return ResponseHelper::returnError('You are not authorized to view sessions for this store.', 403);
|
||||
}
|
||||
|
||||
$page = (int) ($validated['page'] ?? 1);
|
||||
$perPage = (int) ($validated['per_page'] ?? 10);
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
$query = PosSession::with(['transactions.product'])
|
||||
->where('store_id', $store->id)
|
||||
->where('status', '!=', 'active')
|
||||
->orderBy('id', 'desc');
|
||||
|
||||
$totalCount = $query->count();
|
||||
$sessions = $query->limit($perPage)->offset($offset)->get();
|
||||
|
||||
// Map sessions to include item count and simplify if needed
|
||||
$sessions = $sessions->map(function ($session) {
|
||||
return [
|
||||
'hashkey' => $session->hashkey,
|
||||
'status' => $session->status,
|
||||
'total_amount' => $session->total_amount,
|
||||
'customer_name' => $session->customer_name,
|
||||
'payment_method' => $session->payment_method,
|
||||
'items_count' => $session->transactions->count(),
|
||||
'created_at' => $session->created_at,
|
||||
'transactions' => $session->transactions,
|
||||
];
|
||||
});
|
||||
|
||||
return ResponseHelper::returnSuccessResponse([
|
||||
'sessions' => $sessions,
|
||||
'total_count' => $totalCount,
|
||||
'page' => $page,
|
||||
'per_page' => $perPage,
|
||||
], $store->hashkey, 'POS sessions fetched');
|
||||
}
|
||||
|
||||
public function getTodayStats(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewPosReports)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$date = now()->format('Y-m-d');
|
||||
$query = PosSession::where('status', 'completed')
|
||||
->whereDate('created_at', $date);
|
||||
|
||||
$storeName = null;
|
||||
$storePhoto = null;
|
||||
// If store_hash is provided, filter by store
|
||||
if ($request->input('store_hash')) {
|
||||
$store = Store::where('hashkey', $request->input('store_hash'))->first();
|
||||
if ($store) {
|
||||
// Authorization check
|
||||
if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $store)) {
|
||||
return ResponseHelper::returnError('You are not authorized to view reports for this store.', 403);
|
||||
}
|
||||
$query->where('store_id', $store->id);
|
||||
$storeName = $store->name;
|
||||
$storePhoto = $store->photourl;
|
||||
}
|
||||
}
|
||||
|
||||
$stats = CacheHelper::get_cache($query);
|
||||
if ($stats) {
|
||||
return ResponseHelper::returnSuccessResponse($stats, 'today_stats');
|
||||
}
|
||||
|
||||
$stats = [
|
||||
'count' => (int) $query->count(),
|
||||
'total' => (int) $query->sum('total_amount'),
|
||||
'store_name' => $storeName,
|
||||
'store_photo' => $storePhoto,
|
||||
];
|
||||
|
||||
CacheHelper::set_cache($query, $stats, [], 300); // 5 minutes
|
||||
|
||||
return ResponseHelper::returnSuccessResponse($stats, 'today_stats');
|
||||
}
|
||||
|
||||
public function getCustomers(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewCustomers)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$queryText = $request->input('query');
|
||||
$storeHash = $request->input('store_hash');
|
||||
|
||||
$customerQuery = Customer::where('is_active', true);
|
||||
|
||||
if ($storeHash) {
|
||||
$store = Store::where('hashkey', $storeHash)->first();
|
||||
if ($store) {
|
||||
// Authorization check
|
||||
if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $store)) {
|
||||
return ResponseHelper::returnError('You are not authorized to view customers for this store.', 403);
|
||||
}
|
||||
$customerQuery->where(function ($q) use ($store) {
|
||||
$q->where('store_id', $store->id)
|
||||
->orWhereNull('store_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if ($queryText) {
|
||||
$customerQuery->where('name', 'like', '%' . $queryText . '%');
|
||||
}
|
||||
|
||||
$finalQuery = $customerQuery->orderBy('name', 'asc')->limit(10);
|
||||
$customers = CacheHelper::get_cache($finalQuery);
|
||||
|
||||
if (!$customers) {
|
||||
$customers = $finalQuery->get();
|
||||
CacheHelper::set_cache($finalQuery, $customers, [], 3600); // 1 hour
|
||||
}
|
||||
|
||||
return ResponseHelper::returnSuccessResponse($customers, 'customer_suggestions');
|
||||
}
|
||||
|
||||
private function updateSessionTotals(PosSession $session)
|
||||
{
|
||||
$total = $session->transactions()->where('is_void', false)->sum('total_price');
|
||||
$session->total_amount = (int) $total;
|
||||
$session->save();
|
||||
}
|
||||
|
||||
private function archiveSession(PosSession $session, string $remarks = '', $transactions = null)
|
||||
{
|
||||
// Serialize all data NOW before spawning coroutine to avoid context issues
|
||||
$sessionData = $session->toArray();
|
||||
$sessionId = $session->id;
|
||||
$sessionHashkey = $session->hashkey;
|
||||
$userId = Auth::id();
|
||||
|
||||
if ($transactions !== null) {
|
||||
$transactionsData = $transactions->toArray();
|
||||
} else {
|
||||
$transactionsData = $session->transactions()->with('product')->get()->toArray();
|
||||
}
|
||||
|
||||
// Defer the archive INSERT to a background coroutine so it doesn't block the response
|
||||
Coroutine::create(function () use ($sessionId, $sessionHashkey, $sessionData, $transactionsData, $userId, $remarks) {
|
||||
try {
|
||||
PosSessionArchive::create([
|
||||
'pos_session_id' => $sessionId,
|
||||
'hashkey' => $sessionHashkey,
|
||||
'session_snapshot' => $sessionData,
|
||||
'transactions_snapshot' => $transactionsData,
|
||||
'created_by' => $userId,
|
||||
'remarks' => $remarks,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
// Silently fail — archive is non-critical audit data
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function invalidateSessionCache(PosSession $session)
|
||||
{
|
||||
// 1. Invalidate by hashkey (with relations)
|
||||
CacheHelper::erase_cache($this->getBaseSessionQuery()->where('hashkey', $session->hashkey));
|
||||
|
||||
// 1b. Invalidate simple hashkey lookup (used in addItem)
|
||||
CacheHelper::erase_cache(PosSession::where('hashkey', $session->hashkey));
|
||||
|
||||
// 2. Invalidate by store_id (last active session)
|
||||
if ($session->store_id) {
|
||||
CacheHelper::erase_cache($this->getBaseSessionQuery()
|
||||
->where('store_id', $session->store_id)
|
||||
->where('status', 'active')
|
||||
->orderBy('id', 'desc'));
|
||||
}
|
||||
|
||||
// 3. Invalidate by access_key
|
||||
if ($session->access_key) {
|
||||
CacheHelper::erase_cache($this->getBaseSessionQuery()
|
||||
->where('access_key', $session->access_key)
|
||||
->orderBy('id', 'desc'));
|
||||
}
|
||||
// 4. Invalidate today stats cache for the store
|
||||
if ($session->store_id) {
|
||||
$date = now()->format('Y-m-d');
|
||||
$statsQuery = PosSession::where('status', 'completed')
|
||||
->whereDate('created_at', $date)
|
||||
->where('store_id', $session->store_id);
|
||||
CacheHelper::erase_cache($statsQuery);
|
||||
}
|
||||
}
|
||||
}
|
||||
1140
app/Http/Controllers/Market/ProductController.php
Normal file
1140
app/Http/Controllers/Market/ProductController.php
Normal file
File diff suppressed because it is too large
Load Diff
190
app/Http/Controllers/Market/ProductPhotoSearchController.php
Normal file
190
app/Http/Controllers/Market/ProductPhotoSearchController.php
Normal file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Market;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\FilesMainController;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Enums\UserActions;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
|
||||
class ProductPhotoSearchController extends Controller
|
||||
{
|
||||
private const DDG_BROWSER_HEADERS = [
|
||||
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
||||
'Accept-Language: en-US,en;q=0.9',
|
||||
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Referer: https://duckduckgo.com/',
|
||||
];
|
||||
|
||||
// Step 1: fetch the DDG search page to extract the vqd session token.
|
||||
// DDG requires this token to serve the image JSON endpoint.
|
||||
private static function getDdgVqd(string $query): ?string
|
||||
{
|
||||
$url = 'https://duckduckgo.com/?q=' . urlencode($query) . '&iax=images&ia=images';
|
||||
$ctx = stream_context_create(['http' => [
|
||||
'method' => 'GET',
|
||||
'header' => implode("\r\n", self::DDG_BROWSER_HEADERS),
|
||||
'timeout' => 10,
|
||||
]]);
|
||||
$html = @file_get_contents($url, false, $ctx);
|
||||
if (!$html) return null;
|
||||
// The vqd token appears in the page JS in several formats depending on
|
||||
// DDG's current build. Try the quoted forms first (vqd="4-xxx" /
|
||||
// vqd='4-xxx' / vqd:"4-xxx"), then fall back to the bare form.
|
||||
$patterns = [
|
||||
'/vqd=["\']([0-9a-zA-Z._\-]+)["\']/', // vqd="4-123..." or vqd='4-123...'
|
||||
'/vqd["\']?\s*[:=]\s*["\']([0-9a-zA-Z._\-]+)["\']/', // vqd:"4-123..."
|
||||
'/vqd=([0-9a-zA-Z._\-]+)/', // bare vqd=4-123...
|
||||
];
|
||||
foreach ($patterns as $pattern) {
|
||||
if (preg_match($pattern, $html, $m) && !empty($m[1])) {
|
||||
return $m[1];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// GET /api/products/photo-search?q=...&page=1
|
||||
public function search(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::SearchStockPhotos)) {
|
||||
return response()->json(['error' => 'Unauthorized'], 403);
|
||||
}
|
||||
|
||||
$query = trim($request->input('q', ''));
|
||||
$page = max(1, (int) $request->input('page', 1));
|
||||
|
||||
if (!$query) {
|
||||
return response()->json(['error' => 'Query required'], 422);
|
||||
}
|
||||
|
||||
$vqd = self::getDdgVqd($query);
|
||||
if (!$vqd) {
|
||||
return response()->json(['error' => 'Could not reach image search service'], 502);
|
||||
}
|
||||
|
||||
// s = offset; DDG returns ~15 results per call; page 1 = s=0, page 2 = s=15, etc.
|
||||
$offset = ($page - 1) * 15;
|
||||
|
||||
$url = 'https://duckduckgo.com/i.js?' . http_build_query([
|
||||
'q' => $query,
|
||||
'vqd' => $vqd,
|
||||
'o' => 'json',
|
||||
'p' => '1',
|
||||
'f' => ',,,',
|
||||
'l' => 'us-en',
|
||||
's' => $offset,
|
||||
]);
|
||||
|
||||
$ctx = stream_context_create(['http' => [
|
||||
'method' => 'GET',
|
||||
'header' => implode("\r\n", self::DDG_BROWSER_HEADERS),
|
||||
'timeout' => 10,
|
||||
]]);
|
||||
|
||||
$raw = @file_get_contents($url, false, $ctx);
|
||||
if ($raw === false) {
|
||||
return response()->json(['error' => 'Failed to fetch image results'], 502);
|
||||
}
|
||||
|
||||
$data = json_decode($raw, true);
|
||||
$results = $data['results'] ?? [];
|
||||
|
||||
$photos = array_map(fn($r) => [
|
||||
'id' => md5($r['image']), // stable ID from image URL
|
||||
'thumb' => $r['thumbnail'], // DDG-proxied small thumb (safe to display)
|
||||
'src' => $r['image'], // actual source image URL (used for download)
|
||||
'title' => $r['title'] ?? '',
|
||||
], array_slice($results, 0, 15));
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'photos' => $photos,
|
||||
'page' => $page,
|
||||
'has_more' => count($results) >= 15,
|
||||
]);
|
||||
}
|
||||
|
||||
// POST /api/products/photo-download
|
||||
// body: { src: "https://..." } — the actual source image URL from DDG results
|
||||
public function download(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::DownloadStockPhoto)) {
|
||||
return response()->json(['error' => 'Unauthorized'], 403);
|
||||
}
|
||||
|
||||
$src = $request->input('src', '');
|
||||
|
||||
// SSRF guard: must be http/https and must not target private/loopback IPs
|
||||
$parsed = parse_url($src);
|
||||
$scheme = $parsed['scheme'] ?? '';
|
||||
$host = strtolower($parsed['host'] ?? '');
|
||||
|
||||
if (!in_array($scheme, ['http', 'https']) || !$host) {
|
||||
return response()->json(['error' => 'Invalid URL'], 422);
|
||||
}
|
||||
|
||||
// Block private/loopback ranges
|
||||
if (preg_match('/^(localhost|127\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|0\.0\.0\.0|::1)/i', $host)) {
|
||||
return response()->json(['error' => 'Forbidden URL'], 403);
|
||||
}
|
||||
|
||||
$ctx = stream_context_create(['http' => [
|
||||
'method' => 'GET',
|
||||
'header' => 'User-Agent: Mozilla/5.0' . "\r\n",
|
||||
'timeout' => 15,
|
||||
]]);
|
||||
$raw = @file_get_contents($src, false, $ctx);
|
||||
if ($raw === false || strlen($raw) < 500) {
|
||||
return response()->json(['error' => 'Failed to fetch image'], 502);
|
||||
}
|
||||
|
||||
// Resize to max 1280x720 using PHP GD (bundled — no Intervention Image needed)
|
||||
$srcImg = @imagecreatefromstring($raw);
|
||||
if (!$srcImg) {
|
||||
return response()->json(['error' => 'Invalid image data'], 422);
|
||||
}
|
||||
|
||||
$origW = imagesx($srcImg);
|
||||
$origH = imagesy($srcImg);
|
||||
$maxW = 1280;
|
||||
$maxH = 720;
|
||||
|
||||
$ratio = min($maxW / $origW, $maxH / $origH, 1.0); // never upscale
|
||||
$newW = (int) round($origW * $ratio);
|
||||
$newH = (int) round($origH * $ratio);
|
||||
|
||||
$dstImg = imagescale($srcImg, $newW, $newH, IMG_BILINEAR_FIXED);
|
||||
imagedestroy($srcImg);
|
||||
|
||||
ob_start();
|
||||
imagejpeg($dstImg, null, 85);
|
||||
$binary = ob_get_clean();
|
||||
imagedestroy($dstImg);
|
||||
|
||||
// Save via existing pipeline — binary string branch in uploadFileContent handles this
|
||||
$result = FilesMainController::uploadFileList(
|
||||
$binary,
|
||||
'stock-photo',
|
||||
'stock_photo_' . time() . '.jpg',
|
||||
'',
|
||||
[],
|
||||
'ProductMarket',
|
||||
[],
|
||||
0,
|
||||
'image/jpeg'
|
||||
);
|
||||
|
||||
if (!$result || empty($result->hashkey)) {
|
||||
return response()->json(['error' => 'Save failed'], 500);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'hashkey' => $result->hashkey,
|
||||
'url' => $result->resolvedUrl(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
163
app/Http/Controllers/Market/ShipmentController.php
Normal file
163
app/Http/Controllers/Market/ShipmentController.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Market;
|
||||
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\Market\Courier;
|
||||
use App\Models\Market\Shipment;
|
||||
use App\Models\Market\Store;
|
||||
use App\Models\Market\Customer;
|
||||
use App\Models\GlobalTransaction;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Enums\UserActions;
|
||||
|
||||
class ShipmentController
|
||||
{
|
||||
public function listShipments(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewShipments)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
|
||||
$query = Shipment::with(['courier', 'transaction', 'store', 'customer']);
|
||||
|
||||
// filter by store if provided
|
||||
if ($storeHash = $request->input('store_hash')) {
|
||||
$store = Store::where('hashkey', $storeHash)->first();
|
||||
if ($store) {
|
||||
$query->where('store_id', $store->id);
|
||||
}
|
||||
}
|
||||
|
||||
// if not ultimate/admin, restrict to user's shipments
|
||||
// (This logic might need adjustment based on how roles are defined)
|
||||
// For now, let's just list all and allow filtering
|
||||
|
||||
$shipments = $query->orderBy('created_at', 'desc')->get();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $shipments
|
||||
]);
|
||||
}
|
||||
|
||||
public function createNewShipment(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::CreateShipment)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
|
||||
$validated = $request->validate([
|
||||
'transaction_hash' => 'required|string',
|
||||
'store_hash' => 'nullable|string',
|
||||
'customer_hash' => 'nullable|string',
|
||||
'courier_hash' => 'nullable|string',
|
||||
'origin_address' => 'nullable|string',
|
||||
'destination_address' => 'nullable|string',
|
||||
'shipping_fee' => 'nullable|numeric',
|
||||
'estimated_delivery_date' => 'nullable|date',
|
||||
]);
|
||||
|
||||
$transaction = GlobalTransaction::where('hashkey', $validated['transaction_hash'])->first();
|
||||
if (!$transaction) {
|
||||
return ResponseHelper::returnError('Transaction not found', 404);
|
||||
}
|
||||
|
||||
$store = $validated['store_hash'] ? Store::where('hashkey', $validated['store_hash'])->first() : null;
|
||||
$customer = $validated['customer_hash'] ? Customer::where('hashkey', $validated['customer_hash'])->first() : null;
|
||||
$courier = $validated['courier_hash'] ? Courier::where('hashkey', $validated['courier_hash'])->first() : null;
|
||||
|
||||
$shipment = new Shipment([
|
||||
'transaction_id' => $transaction->id,
|
||||
'store_id' => $store?->id,
|
||||
'customer_id' => $customer?->id,
|
||||
'courier_id' => $courier?->id,
|
||||
'origin_address' => $validated['origin_address'] ?? $store?->address,
|
||||
'destination_address' => $validated['destination_address'] ?? $customer?->address,
|
||||
'shipping_fee' => $validated['shipping_fee'] ?? 0,
|
||||
'estimated_delivery_date' => $validated['estimated_delivery_date'],
|
||||
'status' => 'PENDING',
|
||||
'created_by' => $user->id,
|
||||
]);
|
||||
|
||||
if ($shipment->save()) {
|
||||
return ResponseHelper::returnSuccessResponse($shipment, $shipment->hashkey, 'Shipment created successfully');
|
||||
}
|
||||
|
||||
return ResponseHelper::returnError('Failed to create shipment');
|
||||
}
|
||||
|
||||
public function updateShipmentStatus(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::UpdateShipmentStatus)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$hashkey = $request->input('target');
|
||||
$status = $request->input('status');
|
||||
|
||||
if (!$hashkey || !$status) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$shipment = Shipment::where('hashkey', $hashkey)->first();
|
||||
if (!$shipment) {
|
||||
return ResponseHelper::returnError('Shipment not found', 404);
|
||||
}
|
||||
|
||||
$shipment->status = $status;
|
||||
if ($status === 'DELIVERED') {
|
||||
$shipment->actual_delivery_date = now();
|
||||
}
|
||||
$shipment->save();
|
||||
|
||||
return ResponseHelper::returnSuccessResponse($shipment, $shipment->hashkey, 'Shipment status updated');
|
||||
}
|
||||
|
||||
public function listCouriers()
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewCouriers)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$couriers = Courier::where('is_active', true)->get();
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $couriers
|
||||
]);
|
||||
}
|
||||
|
||||
public function createCourier(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::CreateCourier)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'contact_number' => 'nullable|string',
|
||||
'type' => 'required|string|in:INTERNAL,EXTERNAL',
|
||||
]);
|
||||
|
||||
$courier = new Courier([
|
||||
'name' => $validated['name'],
|
||||
'contact_number' => $validated['contact_number'],
|
||||
'type' => $validated['type'],
|
||||
]);
|
||||
|
||||
if ($courier->save()) {
|
||||
return ResponseHelper::returnSuccessResponse($courier, $courier->hashkey, 'Courier created');
|
||||
}
|
||||
|
||||
return ResponseHelper::returnError('Failed to create courier');
|
||||
}
|
||||
}
|
||||
1510
app/Http/Controllers/Market/StoreController.php
Normal file
1510
app/Http/Controllers/Market/StoreController.php
Normal file
File diff suppressed because it is too large
Load Diff
565
app/Http/Controllers/Market/UltimateController.php
Normal file
565
app/Http/Controllers/Market/UltimateController.php
Normal file
@@ -0,0 +1,565 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Market;
|
||||
|
||||
use App\Enums\UserActions;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\User;
|
||||
use App\Models\Market\Store;
|
||||
use App\Models\Market\Product;
|
||||
use App\Models\GlobalTransaction;
|
||||
use App\Models\FileContent;
|
||||
use App\Models\DbBackup;
|
||||
use Hyperf\Stringable\Str;
|
||||
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\DB;
|
||||
use Hypervel\Support\Facades\Redis;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
|
||||
class UltimateController
|
||||
{
|
||||
/**
|
||||
* Common check for Ultimate access.
|
||||
*/
|
||||
private function checkAccess()
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(0, UserActions::UltimateConsole)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get system-wide statistics for the dashboard.
|
||||
*/
|
||||
public function getSystemStats()
|
||||
{
|
||||
if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
$globalMessage = Redis::get('system:global_message');
|
||||
|
||||
$redisStatus = ['connected' => false, 'ping_ms' => null, 'used_memory_human' => null, 'version' => null, 'error' => null];
|
||||
try {
|
||||
$start = microtime(true);
|
||||
$pong = Redis::ping();
|
||||
$redisStatus['ping_ms'] = round((microtime(true) - $start) * 1000, 2);
|
||||
$redisStatus['connected'] = $pong === true || $pong === 'PONG' || $pong === '+PONG' || (is_string($pong) && stripos($pong, 'PONG') !== false);
|
||||
|
||||
$info = Redis::info();
|
||||
if (is_array($info)) {
|
||||
$flat = isset($info['Memory']) && is_array($info['Memory']) ? $info['Memory'] : $info;
|
||||
$redisStatus['used_memory_human'] = $flat['used_memory_human'] ?? null;
|
||||
$serverInfo = isset($info['Server']) && is_array($info['Server']) ? $info['Server'] : $info;
|
||||
$redisStatus['version'] = $serverInfo['redis_version'] ?? null;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$redisStatus['error'] = $e->getMessage();
|
||||
}
|
||||
|
||||
$stats = [
|
||||
'users' => User::count(),
|
||||
'active_users' => User::where('active', true)->count(),
|
||||
'stores' => Store::count(),
|
||||
'active_stores' => Store::where('is_active', true)->count(),
|
||||
'products' => Product::count(),
|
||||
'transactions' => GlobalTransaction::count(),
|
||||
'total_balance' => GlobalTransaction::sum('amount'),
|
||||
'php_version' => PHP_VERSION,
|
||||
'server_time' => date('Y-m-d H:i:s'),
|
||||
'maintenance_mode' => Redis::get('system:maintenance_mode') === 'true',
|
||||
'global_message' => $globalMessage ? json_decode($globalMessage, true) : null,
|
||||
'logs_count' => DB::table('logs')->count(),
|
||||
'table_logs_count' => DB::table('table_logs')->count(),
|
||||
'pos_sessions_count' => DB::table('pos_sessions')->count(),
|
||||
'cooperatives_count' => DB::table('organizations')->where('type', 'COOPERATIVE')->count(),
|
||||
'carts_count' => DB::table('carts')->count(),
|
||||
'farmer_profiles_count' => DB::table('farmer_profiles')->count(),
|
||||
'redis' => $redisStatus,
|
||||
];
|
||||
|
||||
return Response::json(['success' => true, 'data' => $stats]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a raw SQL query.
|
||||
*/
|
||||
public function runQuery(Request $request)
|
||||
{
|
||||
if (Auth::user()->acct_type !== \App\Enums\UserTypes::ULTIMATE || !UserPermissions::isActionPermitted(0, UserActions::UltimateQuery)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$query = $request->input('query');
|
||||
if (empty($query)) return ResponseHelper::returnError('Query cannot be empty');
|
||||
|
||||
try {
|
||||
$queryLower = strtolower(trim($query));
|
||||
if (str_starts_with($queryLower, 'select') || str_starts_with($queryLower, 'show') || str_starts_with($queryLower, 'describe')) {
|
||||
$results = DB::select($query);
|
||||
return Response::json(['success' => true, 'data' => $results]);
|
||||
} else {
|
||||
$affected = DB::statement($query);
|
||||
return Response::json(['success' => true, 'affected' => $affected]);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
return ResponseHelper::returnError($th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle maintenance mode system-wide.
|
||||
*/
|
||||
public function toggleMaintenance(Request $request)
|
||||
{
|
||||
if (!$this->checkAccess() || !UserPermissions::isActionPermitted(0, UserActions::UltimateMaintenance)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$enabled = (bool) $request->input('enabled');
|
||||
Redis::set('system:maintenance_mode', $enabled ? 'true' : 'false');
|
||||
|
||||
return Response::json(['success' => true, 'maintenance_mode' => $enabled]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a global message / broadcast.
|
||||
*/
|
||||
public function sendGlobalMessage(Request $request)
|
||||
{
|
||||
if (!$this->checkAccess() || !UserPermissions::isActionPermitted(0, UserActions::UltimateGlobalMessage)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$message = $request->input('message');
|
||||
$type = $request->input('type', 'info'); // info, success, warning, danger
|
||||
|
||||
if (empty($message)) {
|
||||
Redis::del('system:global_message');
|
||||
return Response::json(['success' => true, 'message' => 'Global message cleared']);
|
||||
}
|
||||
|
||||
Redis::set('system:global_message', json_encode([
|
||||
'text' => $message,
|
||||
'type' => $type,
|
||||
'timestamp' => time()
|
||||
]));
|
||||
|
||||
return Response::json(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush / Truncate specific tables.
|
||||
*/
|
||||
public function flushData(Request $request)
|
||||
{
|
||||
if (!$this->checkAccess() || !UserPermissions::isActionPermitted(0, UserActions::UltimateFlush)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$target = $request->input('target');
|
||||
|
||||
try {
|
||||
$affected = 0;
|
||||
switch ($target) {
|
||||
case 'transactions':
|
||||
$affected = GlobalTransaction::count();
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 0');
|
||||
GlobalTransaction::truncate();
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
|
||||
break;
|
||||
case 'pos_sessions':
|
||||
$affected = DB::table('pos_sessions')->count();
|
||||
DB::table('pos_sessions')->truncate();
|
||||
break;
|
||||
case 'cache':
|
||||
Redis::flushDB();
|
||||
break;
|
||||
case 'stores':
|
||||
$affected = DB::table('str')->count();
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 0');
|
||||
DB::table('prd_str')->truncate();
|
||||
DB::table('store_managers')->truncate();
|
||||
DB::table('str')->truncate();
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
|
||||
break;
|
||||
case 'products':
|
||||
$affected = DB::table('prd_items')->count();
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 0');
|
||||
DB::table('prd_str')->truncate();
|
||||
DB::table('prd_items')->truncate();
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
|
||||
break;
|
||||
case 'cooperatives':
|
||||
$affected = DB::table('organizations')->where('type', 'COOPERATIVE')->count();
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 0');
|
||||
DB::table('cooperative_votes')->truncate();
|
||||
DB::table('cooperative_resolutions')->truncate();
|
||||
DB::table('cooperative_documents')->truncate();
|
||||
DB::table('cooperative_members')->truncate();
|
||||
DB::table('organizations')->where('type', 'COOPERATIVE')->delete();
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
|
||||
break;
|
||||
case 'carts':
|
||||
$affected = DB::table('carts')->count();
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 0');
|
||||
DB::table('cart_items')->truncate();
|
||||
DB::table('carts')->truncate();
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
|
||||
break;
|
||||
case 'farmer_profiles':
|
||||
$affected = DB::table('farmer_profiles')->count();
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 0');
|
||||
DB::table('farmer_profiles')->truncate();
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS = 1');
|
||||
break;
|
||||
default:
|
||||
return ResponseHelper::returnError('Invalid flush target');
|
||||
}
|
||||
return Response::json(['success' => true, 'message' => "Flushed $target successfully", 'affected' => $affected]);
|
||||
} catch (\Throwable $th) {
|
||||
return ResponseHelper::returnError($th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a test notification for a specific user.
|
||||
*/
|
||||
public function testNotification(Request $request)
|
||||
{
|
||||
if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
$userHash = $request->input('user_hash');
|
||||
$user = User::where('hashkey', $userHash)->first();
|
||||
if (!$user) return ResponseHelper::returnError('User not found');
|
||||
|
||||
// Setting exec_command which SSEController picks up to notify client
|
||||
$user->exec_command = 'toast:success:Test Notification from Ultimate Console: ' . date('H:i:s');
|
||||
$user->save();
|
||||
|
||||
return Response::json(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch management for various entities.
|
||||
*/
|
||||
public function batchManage(Request $request)
|
||||
{
|
||||
if (!$this->checkAccess() || !UserPermissions::isActionPermitted(0, UserActions::UltimateBatch)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$action = $request->input('action');
|
||||
$ids = $request->input('ids', []);
|
||||
$data = $request->input('data', []);
|
||||
|
||||
if (empty($ids) && !in_array($action, ['cleanup_sessions'])) {
|
||||
return ResponseHelper::returnError('No IDs provided');
|
||||
}
|
||||
|
||||
try {
|
||||
switch($action) {
|
||||
case 'activate_users':
|
||||
User::whereIn('id', $ids)->update(['active' => true]);
|
||||
break;
|
||||
case 'deactivate_users':
|
||||
User::whereIn('id', $ids)->update(['active' => false]);
|
||||
break;
|
||||
case 'cleanup_sessions':
|
||||
DB::table('pos_sessions')->where('status', 'VOIDED')->delete();
|
||||
break;
|
||||
case 'mass_transfer_points':
|
||||
$amount = (float)($data['amount'] ?? 0);
|
||||
if ($amount <= 0) return ResponseHelper::returnError('Invalid amount');
|
||||
|
||||
foreach ($ids as $id) {
|
||||
GlobalTransaction::create([
|
||||
'user_id' => $id,
|
||||
'amount' => $amount,
|
||||
'type' => 'REWARD',
|
||||
'description' => 'Mass points adjustment via Ultimate Console',
|
||||
'is_active' => true,
|
||||
]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return ResponseHelper::returnError('Invalid batch action');
|
||||
}
|
||||
return Response::json(['success' => true]);
|
||||
} catch (\Throwable $th) {
|
||||
return ResponseHelper::returnError($th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a system command (Artisan wrapper).
|
||||
*/
|
||||
public function runCommand(Request $request)
|
||||
{
|
||||
if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
$command = $request->input('command');
|
||||
if (empty($command)) return ResponseHelper::returnError('Command cannot be empty');
|
||||
|
||||
// Normalize command: strip 'php artisan ' if present
|
||||
$command = preg_replace('/^php artisan\s+/', '', trim($command));
|
||||
|
||||
// Mapping for user-friendly commands
|
||||
if ($command === 'reset-app all users') {
|
||||
$command = 'app:reset-users';
|
||||
}
|
||||
|
||||
if ($command === 'db seed') {
|
||||
$command = 'db:seed';
|
||||
}
|
||||
|
||||
// For security, only allow specific commands
|
||||
$allowedCommands = [
|
||||
'cache:clear', 'view:clear', 'config:clear', 'route:clear',
|
||||
'migrate', 'migrate:rollback', 'migrate:fresh',
|
||||
'db:seed', 'app:reset-users', 'optimize', 'optimize:clear'
|
||||
];
|
||||
|
||||
$baseCommand = explode(' ', trim($command))[0];
|
||||
|
||||
if (!in_array($baseCommand, $allowedCommands)) {
|
||||
return ResponseHelper::returnError("Command '{$baseCommand}' not allowed for security reasons.");
|
||||
}
|
||||
|
||||
try {
|
||||
// In Hyperf, running commands from HTTP request context is tricky.
|
||||
// We'll use shell_exec in this local environment demo as a fallback.
|
||||
$output = shell_exec("php artisan $command 2>&1");
|
||||
return Response::json(['success' => true, 'output' => $output]);
|
||||
} catch (\Throwable $th) {
|
||||
return ResponseHelper::returnError($th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run `php artisan migrate --force` (non-interactive).
|
||||
*/
|
||||
public function runMigrate(Request $request)
|
||||
{
|
||||
if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
try {
|
||||
$output = shell_exec('cd ' . escapeshellarg(BASE_PATH) . ' && php artisan migrate --force 2>&1');
|
||||
return Response::json(['success' => true, 'output' => $output]);
|
||||
} catch (\Throwable $th) {
|
||||
return ResponseHelper::returnError($th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a full database backup.
|
||||
* Puts system in maintenance mode during the process.
|
||||
*/
|
||||
public function downloadBackup()
|
||||
{
|
||||
if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
// 1. Enable maintenance mode & Notify
|
||||
Redis::set('system:maintenance_mode', 'true');
|
||||
Redis::set('system:global_message', json_encode(['text' => 'System backup in progress. Transactions temporarily disabled.', 'type' => 'warning']));
|
||||
|
||||
try {
|
||||
$filename = 'backup_' . date('Y-m-d_H-i-s') . '.sql';
|
||||
$path = BASE_PATH . '/storage/app/backups/' . $filename;
|
||||
|
||||
if (!is_dir(dirname($path))) {
|
||||
mkdir(dirname($path), 0755, true);
|
||||
}
|
||||
|
||||
$dbHost = env('DB_HOST', '127.0.0.1');
|
||||
$dbPort = env('DB_PORT', '3306');
|
||||
$dbName = env('DB_DATABASE', 'bukid');
|
||||
$dbUser = env('DB_USERNAME', 'root');
|
||||
$dbPass = env('DB_PASSWORD', '');
|
||||
|
||||
$dump = new \Ifsnop\Mysqldump\Mysqldump(
|
||||
"mysql:host={$dbHost};port={$dbPort};dbname={$dbName}",
|
||||
$dbUser,
|
||||
$dbPass,
|
||||
[
|
||||
'add-drop-table' => true,
|
||||
'exclude-tables' => ['db_backups'] // Exclude the backups table
|
||||
]
|
||||
);
|
||||
|
||||
$dump->start($path);
|
||||
|
||||
if (!file_exists($path) || filesize($path) === 0) {
|
||||
throw new \Exception('Backup file was not created or is empty.');
|
||||
}
|
||||
|
||||
// Compress into 7z Ultra
|
||||
$sevenZFilename = 'backup_' . date('Y-m-d_H-i-s') . '.7z';
|
||||
$sevenZPath = BASE_PATH . '/storage/app/backups/' . $sevenZFilename;
|
||||
|
||||
// -mx=9 for Ultra compression
|
||||
$path_escaped = escapeshellarg($path);
|
||||
$sevenZPath_escaped = escapeshellarg($sevenZPath);
|
||||
$command = "7z a -t7z -m0=lzma2 -mx=9 {$sevenZPath_escaped} {$path_escaped} 2>&1";
|
||||
shell_exec($command);
|
||||
|
||||
if (!file_exists($sevenZPath)) {
|
||||
throw new \Exception('Failed to create 7z archive.');
|
||||
}
|
||||
|
||||
// Save to database
|
||||
$fileContentRaw = file_get_contents($sevenZPath);
|
||||
$fileHash = hash('sha256', $fileContentRaw);
|
||||
|
||||
$fileContent = new FileContent();
|
||||
$fileContent->filehash = $fileHash;
|
||||
$fileContent->titlename = $sevenZFilename;
|
||||
$fileContent->description = 'System database backup';
|
||||
$fileContent->size_in_bytes = filesize($sevenZPath);
|
||||
$fileContent->content = base64_encode($fileContentRaw);
|
||||
$fileContent->mimetype = 'application/x-7z-compressed';
|
||||
$fileContent->created_by = Auth::id();
|
||||
$fileContent->updated_by = Auth::id();
|
||||
$fileContent->save();
|
||||
|
||||
$dbBackup = new DbBackup();
|
||||
$dbBackup->file_content_hashkey = $fileContent->hashkey;
|
||||
$dbBackup->filename = $sevenZFilename;
|
||||
$dbBackup->size_in_bytes = filesize($sevenZPath);
|
||||
$dbBackup->created_by = Auth::id();
|
||||
$dbBackup->updated_by = Auth::id();
|
||||
$dbBackup->save();
|
||||
|
||||
// Clean up the temporary files from filesystem
|
||||
@unlink($path);
|
||||
@unlink($sevenZPath);
|
||||
|
||||
// 2. Disable maintenance mode & Clear Notify
|
||||
Redis::set('system:maintenance_mode', 'false');
|
||||
Redis::del('system:global_message');
|
||||
|
||||
// 3. Return the binary content for download
|
||||
return Response::make($fileContentRaw, 200, [
|
||||
'Content-Type' => 'application/x-7z-compressed',
|
||||
'Content-Disposition' => 'attachment; filename="' . $sevenZFilename . '"',
|
||||
'Content-Length' => strlen($fileContentRaw),
|
||||
]);
|
||||
} catch (\Throwable $th) {
|
||||
Redis::set('system:maintenance_mode', 'false');
|
||||
return ResponseHelper::returnError($th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recently created backups.
|
||||
*/
|
||||
public function getBackups()
|
||||
{
|
||||
if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
$backups = DbBackup::with(['creator'])
|
||||
->orderBy('created_at', 'desc')
|
||||
->limit(50)
|
||||
->get();
|
||||
|
||||
return Response::json(['success' => true, 'data' => $backups]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename a specific backup.
|
||||
*/
|
||||
public function renameBackup(Request $request)
|
||||
{
|
||||
if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
$hash = $request->input('hash');
|
||||
$newName = $request->input('name');
|
||||
|
||||
if (empty($newName)) return ResponseHelper::returnError('Name cannot be empty');
|
||||
|
||||
$backup = DbBackup::where('hashkey', $hash)->first();
|
||||
if (!$backup) return ResponseHelper::returnError('Backup not found');
|
||||
|
||||
$backup->name = $newName;
|
||||
$backup->save();
|
||||
|
||||
return Response::json(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a specific backup.
|
||||
*/
|
||||
public function deleteBackup(Request $request)
|
||||
{
|
||||
if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
$hash = $request->input('hash');
|
||||
$backup = DbBackup::where('hashkey', $hash)->first();
|
||||
if (!$backup) return ResponseHelper::returnError('Backup not found');
|
||||
|
||||
// Delete associated file content
|
||||
FileContent::where('hashkey', $backup->file_content_hashkey)->delete();
|
||||
|
||||
// Delete backup record
|
||||
$backup->delete();
|
||||
|
||||
return Response::json(['success' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a specific backup from the database.
|
||||
*/
|
||||
public function downloadBackupByHash(Request $request)
|
||||
{
|
||||
if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
$hash = $request->input('hash');
|
||||
$backup = DbBackup::where('hashkey', $hash)->first();
|
||||
if (!$backup) return ResponseHelper::returnError('Backup not found');
|
||||
|
||||
$fileContent = FileContent::where('hashkey', $backup->file_content_hashkey)->first();
|
||||
if (!$fileContent) return ResponseHelper::returnError('File content not found');
|
||||
|
||||
$content = base64_decode($fileContent->content);
|
||||
|
||||
return Response::make($content, 200, [
|
||||
'Content-Type' => $fileContent->mimetype,
|
||||
'Content-Disposition' => 'attachment; filename="' . $backup->filename . '"',
|
||||
'Content-Length' => strlen($content),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get system-wide logs from file and database.
|
||||
*/
|
||||
public function getSystemLogs(Request $request)
|
||||
{
|
||||
if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized();
|
||||
|
||||
$type = $request->input('type', 'database');
|
||||
|
||||
if ($type === 'file') {
|
||||
$logPath = BASE_PATH . '/storage/logs/hypervel.log';
|
||||
if (!file_exists($logPath)) {
|
||||
return Response::json(['success' => true, 'data' => 'No file logs found.']);
|
||||
}
|
||||
$logs = shell_exec("tail -n 1000 " . escapeshellarg($logPath));
|
||||
return Response::json(['success' => true, 'data' => $logs]);
|
||||
}
|
||||
|
||||
// Database logs (audit)
|
||||
if ($type === 'audit') {
|
||||
$logs = DB::table('table_logs')->orderBy('id', 'desc')->limit(500)->get();
|
||||
return Response::json(['success' => true, 'data' => $logs]);
|
||||
}
|
||||
|
||||
// Database logs (system)
|
||||
$logs = DB::table('logs')->orderBy('uid', 'desc')->limit(500)->get();
|
||||
return Response::json(['success' => true, 'data' => $logs]);
|
||||
}
|
||||
}
|
||||
169
app/Http/Controllers/Market/UserInfoController.php
Normal file
169
app/Http/Controllers/Market/UserInfoController.php
Normal file
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Market;
|
||||
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\Market\UserInfo;
|
||||
use App\Models\User;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Enums\UserActions;
|
||||
|
||||
class UserInfoController
|
||||
{
|
||||
public function getUserInfo(Request $request, string $hashkey)
|
||||
{
|
||||
$targetUser = User::where('hashkey', $hashkey)->first();
|
||||
if (!$targetUser) {
|
||||
return ResponseHelper::returnError('User not found', 404);
|
||||
}
|
||||
|
||||
$currentUser = Auth::user();
|
||||
if (!$currentUser) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
// Check permission: can view self or has ViewUserInfo permission for others
|
||||
if ($currentUser->id !== $targetUser->id && !UserPermissions::isActionPermitted($currentUser->acct_type, UserActions::ViewUserInfo)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$userInfo = $targetUser->userInfo;
|
||||
if (!$userInfo) {
|
||||
// Lazy create if it doesn't exist (should have been backfilled but just in case)
|
||||
$userInfo = UserInfo::create([
|
||||
'user_id' => $targetUser->id,
|
||||
'fullname' => $targetUser->fullname ?? $targetUser->name,
|
||||
'email' => $targetUser->email,
|
||||
'mobile' => $targetUser->mobile_number,
|
||||
'is_active' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $userInfo
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateUserInfo(Request $request, string $hashkey)
|
||||
{
|
||||
$targetUser = User::where('hashkey', $hashkey)->first();
|
||||
if (!$targetUser) {
|
||||
return ResponseHelper::returnError('User not found', 404);
|
||||
}
|
||||
|
||||
$currentUser = Auth::user();
|
||||
if (!$currentUser) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
// Check permission: can manage self or has ManageUserInfo permission for others
|
||||
if ($currentUser->id !== $targetUser->id && !UserPermissions::isActionPermitted($currentUser->acct_type, UserActions::ManageUserInfo)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$userInfo = $targetUser->userInfo;
|
||||
if (!$userInfo) {
|
||||
$userInfo = new UserInfo(['user_id' => $targetUser->id]);
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'firstname' => 'nullable|string|max:255',
|
||||
'middlename' => 'nullable|string|max:255',
|
||||
'lastname' => 'nullable|string|max:255',
|
||||
'suffix' => 'nullable|string|max:50',
|
||||
'gender' => 'nullable|string|max:50',
|
||||
'dob' => 'nullable|date',
|
||||
'priority_sector' => 'nullable|string|max:255',
|
||||
'messenger_id' => 'nullable|string|max:255',
|
||||
'viber_number' => 'nullable|string|max:255',
|
||||
'tiktok_username' => 'nullable|string|max:255',
|
||||
'region' => 'nullable|string|max:255',
|
||||
'province' => 'nullable|string|max:255',
|
||||
'city' => 'nullable|string|max:255',
|
||||
'barangay' => 'nullable|string|max:255',
|
||||
'civil_status' => 'nullable|string|max:100',
|
||||
'children_count' => 'nullable|integer',
|
||||
'dependent_count' => 'nullable|integer',
|
||||
'education_level' => 'nullable|string|max:255',
|
||||
'course' => 'nullable|string|max:255',
|
||||
'school' => 'nullable|string|max:255',
|
||||
'year_last_attended' => 'nullable|string|max:50',
|
||||
'livelihood_source' => 'nullable|string|max:255',
|
||||
'last_company' => 'nullable|string|max:255',
|
||||
'employer_name' => 'nullable|string|max:255',
|
||||
'last_position' => 'nullable|string|max:255',
|
||||
'occupation' => 'nullable|string|max:255',
|
||||
'last_employment_year' => 'nullable|string|max:50',
|
||||
'monthly_income' => 'nullable|numeric',
|
||||
'tin' => 'nullable|string|max:100',
|
||||
'philhealth_id' => 'nullable|string|max:100',
|
||||
'gov_id' => 'nullable|string|max:100',
|
||||
'id_type' => 'nullable|string|max:100',
|
||||
'id_number' => 'nullable|string|max:100',
|
||||
'beneficiary_type' => 'nullable|string|max:100',
|
||||
'emergency_contact_name' => 'nullable|string|max:255',
|
||||
'emergency_contact_address' => 'nullable|string|max:255',
|
||||
'emergency_contact_phone' => 'nullable|string|max:50',
|
||||
'emergency_contact_relation' => 'nullable|string|max:100',
|
||||
'emergency_contact_user_id' => 'nullable|integer',
|
||||
'fullname' => 'nullable|string|max:255',
|
||||
'landline' => 'nullable|string|max:20',
|
||||
'mobile' => 'nullable|string|max:20',
|
||||
'email' => 'nullable|email|max:255',
|
||||
'alt_email' => 'nullable|email|max:255',
|
||||
'alt_landline' => 'nullable|string|max:20',
|
||||
'alt_mobile' => 'nullable|string|max:20',
|
||||
'facebook_url' => 'nullable|url|max:255',
|
||||
'bank_details' => 'nullable|array',
|
||||
'bank_account_no' => 'nullable|string|max:100',
|
||||
'addresses' => 'nullable|array',
|
||||
'other_details' => 'nullable|array',
|
||||
]);
|
||||
|
||||
// Logic to automatically populate emergency_contact_user_id if phone matches a registered user
|
||||
if (!empty($validated['emergency_contact_phone'])) {
|
||||
$matchedUser = User::where('mobile_number', $validated['emergency_contact_phone'])->first();
|
||||
if ($matchedUser) {
|
||||
$validated['emergency_contact_user_id'] = $matchedUser->id;
|
||||
}
|
||||
}
|
||||
|
||||
$userInfo->fill($validated);
|
||||
|
||||
if ($userInfo->save()) {
|
||||
// Also update core user fields if they match
|
||||
if (isset($validated['fullname'])) $targetUser->fullname = $validated['fullname'];
|
||||
if (isset($validated['email'])) $targetUser->email = $validated['email'];
|
||||
if (isset($validated['mobile'])) $targetUser->mobile_number = $validated['mobile'];
|
||||
$targetUser->save();
|
||||
|
||||
return ResponseHelper::returnSuccessResponse($userInfo, $userInfo->hashkey, 'User info updated');
|
||||
}
|
||||
|
||||
return ResponseHelper::returnError('Failed to update user info');
|
||||
}
|
||||
|
||||
public function searchEmergencyContact(Request $request)
|
||||
{
|
||||
$query = $request->input('q');
|
||||
if (empty($query)) {
|
||||
return response()->json(['success' => true, 'data' => []]);
|
||||
}
|
||||
|
||||
$users = User::where('name', 'like', "%$query%")
|
||||
->orWhere('fullname', 'like', "%$query%")
|
||||
->orWhere('mobile_number', 'like', "%$query%")
|
||||
->limit(10)
|
||||
->get(['id', 'name', 'fullname', 'mobile_number', 'hashkey']);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $users
|
||||
]);
|
||||
}
|
||||
}
|
||||
52
app/Http/Controllers/Market/UserSettingsController.php
Normal file
52
app/Http/Controllers/Market/UserSettingsController.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Market;
|
||||
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\User;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
|
||||
class UserSettingsController
|
||||
{
|
||||
/**
|
||||
* Get the current user's settings.
|
||||
*/
|
||||
public function getSettings()
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
return response()->json($user->settings ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current user's settings.
|
||||
*/
|
||||
public function updateSettings(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$newSettings = $request->all();
|
||||
$currentSettings = $user->settings ?? [];
|
||||
|
||||
// Merge new settings into current settings
|
||||
$updatedSettings = array_merge($currentSettings, $newSettings);
|
||||
|
||||
// Save to database
|
||||
$user->settings = $updatedSettings;
|
||||
$user->save();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'settings' => $user->settings
|
||||
]);
|
||||
}
|
||||
}
|
||||
101
app/Http/Controllers/PageMemoryController.php
Normal file
101
app/Http/Controllers/PageMemoryController.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use Hypervel\Support\Facades\File;
|
||||
use Hypervel\Support\Facades\Cache;
|
||||
|
||||
class PageMemoryController
|
||||
{
|
||||
public static function getPageInMemory(string $viewName, array $data = [], int $ttlSeconds = 100)
|
||||
{
|
||||
$dataHash = sha1(json_encode($data));
|
||||
$cacheKey = 'Cache:Pages:Views:' . $viewName . '-------' . $dataHash;
|
||||
|
||||
return Response::html(
|
||||
cache()->remember($cacheKey, $ttlSeconds, function () use ($viewName, $data) {
|
||||
return view($viewName, $data)->render();
|
||||
})
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public static function readAssetInMemory(string $assetFilename, int $ttlSeconds = 10000, $asset_folder = null): ?string
|
||||
{
|
||||
$assetFilename = ltrim($assetFilename, '/\\');
|
||||
$cacheKey = 'Cache:Assets:Static:' . $assetFilename;
|
||||
if (!$asset_folder) {
|
||||
$filePath = storage_path('app/cache/static/' . $assetFilename);
|
||||
} else {
|
||||
$filePath = $asset_folder . $assetFilename;
|
||||
}
|
||||
|
||||
$data = Cache::get($cacheKey);
|
||||
|
||||
if ($data) {
|
||||
return $data['content'];
|
||||
}
|
||||
|
||||
if (!File::exists($filePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$fileContent = file_get_contents($filePath);
|
||||
$mimeType = File::mimeType($filePath) ?? 'application/octet-stream';
|
||||
|
||||
$data = [
|
||||
'content' => $fileContent,
|
||||
'mime' => $mimeType,
|
||||
];
|
||||
|
||||
Cache::put($cacheKey, $data, $ttlSeconds);
|
||||
|
||||
return $fileContent;
|
||||
}
|
||||
|
||||
public static function readPublicAssetInMemory($assetFilename, $ttlSeconds = 10000)
|
||||
{
|
||||
$asset_folder = public_path('static/');
|
||||
return self::readAssetInMemory($assetFilename, $ttlSeconds, $asset_folder);
|
||||
}
|
||||
|
||||
public static function getAssetInMemory(string $assetFilename, int $ttlSeconds = 10000)
|
||||
{
|
||||
$assetFilename = ltrim($assetFilename, '/\\');
|
||||
$cacheKey = 'Cache:Assets:Static:' . $assetFilename;
|
||||
$filePath = storage_path('app/cache/static/' . $assetFilename);
|
||||
|
||||
$data = Cache::get($cacheKey);
|
||||
if ($data) {
|
||||
return Response::raw($data['content'])
|
||||
->withHeader('Content-Type', $data['mime'] ?? 'text/css')
|
||||
->withHeader('Content-Length', (string) strlen($data['content']))
|
||||
->withStatus(200);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
$fileContent = file_get_contents($filePath);
|
||||
$mimeType = File::mimeType($filePath) ?? 'application/octet-stream';
|
||||
|
||||
// Cache content + mime type
|
||||
$data = [
|
||||
'content' => $fileContent,
|
||||
'mime' => $mimeType,
|
||||
];
|
||||
Cache::put($cacheKey, $data, $ttlSeconds);
|
||||
|
||||
return Response::raw($data['content'])
|
||||
->withHeader('Content-Type', $data['mime'] ?? 'text/css')
|
||||
->withHeader('Content-Length', (string) strlen($data['content']))
|
||||
->withStatus(200);
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
return Response::withStatus(404, 'Not Found ' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
219
app/Http/Controllers/Pages/AccountSettingsPageController.php
Normal file
219
app/Http/Controllers/Pages/AccountSettingsPageController.php
Normal file
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Pages;
|
||||
|
||||
use Hypervel\Http\Request;
|
||||
use App\Models\User;
|
||||
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\Log;
|
||||
use Hypervel\Support\Facades\Redis;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
|
||||
use Hypervel\Support\Facades\Hash;
|
||||
use Hypervel\Support\Facades\Session;
|
||||
|
||||
use App\Http\Controllers\Pages\PageController;
|
||||
|
||||
class AccountSettingsPageController
|
||||
{
|
||||
|
||||
public $JSCommands = [
|
||||
'SetDarkMode' => "UISetDarkMode();"
|
||||
];
|
||||
|
||||
public function listDetails()
|
||||
{
|
||||
$currentuser = User::findOrFail(Auth::id());
|
||||
$res = [];
|
||||
|
||||
$res['photourl'] = $currentuser->photourl[0] ?? '';
|
||||
$res['mobile'] = $currentuser->mobile_number ?? '';
|
||||
$res['name'] = $currentuser->name ?? $currentuser->nickname ?? $currentuser->fullname ?? $currentuser->username ?? '';
|
||||
$res['fullname'] = $currentuser->fullname ?? $currentuser->name ?? '';
|
||||
$res['nickname'] = $currentuser->nickname ?? $currentuser->username ?? '';
|
||||
$res['joined'] = $currentuser->created_at ?? '';
|
||||
$res['referralcode'] = $currentuser->referralcode ?? '';
|
||||
$res['email'] = $currentuser->email ?? '';
|
||||
$res['landline'] = $currentuser->landline ?? '';
|
||||
$res['hashkey'] = $currentuser->hashkey ?? '';
|
||||
$res['total_balance'] = $currentuser->total_balance ?? 0;
|
||||
$res['settings'] = $currentuser->settings ?? [];
|
||||
|
||||
|
||||
return Response::json($res ?: []);
|
||||
}
|
||||
|
||||
public function listSettings()
|
||||
{
|
||||
return Response::json(Auth::user()->settings);
|
||||
}
|
||||
|
||||
|
||||
public function listRunScripts()
|
||||
{
|
||||
$scripts = '';
|
||||
$settings = Auth::user()->settings;
|
||||
$darkmode = $settings['dark_mode'] ?? $settings['darkmode'] ?? false;
|
||||
|
||||
|
||||
|
||||
if ($darkmode) {
|
||||
$scripts .= $this->JSCommands['SetDarkMode'];
|
||||
}
|
||||
|
||||
Response::raw($scripts);
|
||||
}
|
||||
|
||||
public function changepassword(Request $request)
|
||||
{
|
||||
|
||||
$validated = $request->validate([
|
||||
'current_password' => 'required|string',
|
||||
'new_password' => 'required|string|min:6',
|
||||
'new_confirm_password' => 'required|string|same:new_password',
|
||||
]);
|
||||
|
||||
if (!$validated['current_password'] or !$validated['new_password'] or !$validated['new_confirm_password']) {
|
||||
return Response::json(['message' => 'Enter Old Password, New Password and Password Confirmation.'], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$user = User::findOrFail(Auth::id());
|
||||
} catch (\Throwable $th) {
|
||||
return Response::json(['message' => 'Internal server error during credit transfer'], 500);
|
||||
}
|
||||
|
||||
$newhash = Hash::make($validated['current_password']);
|
||||
if (!Hash::check($validated['current_password'], $user->password)) {
|
||||
return Response::json(['message' => 'Your current password is incorrect.'], 400);
|
||||
}
|
||||
|
||||
$user->password = Hash::make($validated['new_password']);
|
||||
$user->save();
|
||||
return Response::json(['message' => 'Password changed successfully'], 200);
|
||||
}
|
||||
|
||||
public function getUserNotes()
|
||||
{
|
||||
try {
|
||||
$user = User::findOrFail(Auth::id());
|
||||
return Response::json($user->notes, 200);
|
||||
} catch (\Throwable $th) {
|
||||
return Response::json(['message' => 'User Not Found!'], 404);
|
||||
}
|
||||
}
|
||||
|
||||
public function clearUserNotes()
|
||||
{
|
||||
try {
|
||||
$user = User::findOrFail(Auth::id());
|
||||
$user->notes='';
|
||||
$user->save();
|
||||
return Response::json(['success' => true], 200);
|
||||
} catch (\Throwable $th) {
|
||||
return Response::json(['message' => 'User Not Found!'], 404);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function logoutnow()
|
||||
{
|
||||
$sessionId = session()?->getId();
|
||||
$user = Auth::user();
|
||||
|
||||
Log::info('[Logout] Attempting logout for session: ' . $sessionId);
|
||||
|
||||
if ($user && isset($user->hashkey)) {
|
||||
// Signal SSE streams to terminate
|
||||
Redis::setex("forced_logout:{$user->hashkey}", 60, "1");
|
||||
Log::info('[Logout] Forced logout signal set for user: ' . $user->hashkey);
|
||||
}
|
||||
|
||||
// Logout from all possible guards
|
||||
Auth::logout();
|
||||
try {
|
||||
if (Auth::guard('jwt')->check()) {
|
||||
Auth::guard('jwt')->logout();
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
// Ignore if JWT guard is not properly configured
|
||||
}
|
||||
|
||||
if (session()) {
|
||||
session()->flush();
|
||||
session()->invalidate();
|
||||
Log::info('[Logout] Session invalidated. New ID: ' . session()->getId());
|
||||
}
|
||||
|
||||
// Forced Redis destruction for THIS session ID (covers multiple prefix formats)
|
||||
if ($sessionId) {
|
||||
$prefix = config('cache.prefix', 'bukidbountyapp_cache');
|
||||
|
||||
// Try idiomatic Cache forget first (handles prefixing automatically)
|
||||
\Hypervel\Support\Facades\Cache::forget($sessionId);
|
||||
|
||||
// Try manual Redis deletion for both common prefix patterns (with and without colon)
|
||||
Redis::del(($prefix ? $prefix . ':' : '') . $sessionId);
|
||||
Redis::del(($prefix ? $prefix : '') . $sessionId);
|
||||
|
||||
Log::info('[Logout] Forced Redis/Cache deletion for session: ' . $sessionId);
|
||||
}
|
||||
|
||||
return redirect('/login?logged_out=1');
|
||||
}
|
||||
|
||||
|
||||
public function updatePhoto(Request $request)
|
||||
{
|
||||
if (!$request->hasFile('photo')) {
|
||||
return Response::json(['success' => false, 'message' => 'No photo uploaded'], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$user = User::findOrFail(Auth::id());
|
||||
$file = $request->file('photo');
|
||||
$filename = $file->getClientFilename();
|
||||
|
||||
// Upload the file using FilesMainController
|
||||
$result = \App\Http\Controllers\FilesMainController::uploadFileList(
|
||||
$file,
|
||||
'User Profile Photo: ' . $user->username,
|
||||
$filename ?? 'profile_photo.jpg',
|
||||
'Uploaded by ' . $user->username,
|
||||
['user_id' => $user->id, 'type' => 'profile_photo'],
|
||||
'user_photos',
|
||||
['profile_photo'],
|
||||
0,
|
||||
'profile_photo',
|
||||
);
|
||||
|
||||
// If it's a response object, it might be an error response from uploadFileList
|
||||
if (is_object($result) && method_exists($result, 'getStatusCode')) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ($result && isset($result->hashkey)) {
|
||||
$photoUrl = $result->resolvedUrl();
|
||||
|
||||
// Update user photoUrl array
|
||||
$user->photourl = [$photoUrl];
|
||||
$user->save();
|
||||
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'message' => 'Photo updated successfully',
|
||||
'url' => $photoUrl
|
||||
]);
|
||||
}
|
||||
|
||||
return Response::json(['success' => false, 'message' => 'Failed to process file upload: No result hashkey.'], 500);
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
return Response::json(['success' => false, 'message' => $th->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
34
app/Http/Controllers/Pages/Core/ApplicationController.php
Normal file
34
app/Http/Controllers/Pages/Core/ApplicationController.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Pages;
|
||||
|
||||
use Hypervel\Http\Request;
|
||||
use App\Models\User;
|
||||
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
|
||||
use Hypervel\Support\Facades\Hash;
|
||||
use Hypervel\Support\Facades\Session;
|
||||
|
||||
use App\Http\Controllers\Pages\Core;
|
||||
|
||||
class ApplicationController
|
||||
{
|
||||
|
||||
public $JSCommands = [
|
||||
'SetDarkMode' => "UISetDarkMode();"
|
||||
];
|
||||
|
||||
public function logout()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
28
app/Http/Controllers/Pages/Core/HomeController.php
Normal file
28
app/Http/Controllers/Pages/Core/HomeController.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Pages;
|
||||
|
||||
use Hypervel\Http\Request;
|
||||
use App\Models\User;
|
||||
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
|
||||
use Hypervel\Support\Facades\Hash;
|
||||
use Hypervel\Support\Facades\Session;
|
||||
|
||||
use App\Http\Controllers\Pages\Core;
|
||||
|
||||
class HomeController
|
||||
{
|
||||
|
||||
public function index()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
20
app/Http/Controllers/Pages/PageController.php
Normal file
20
app/Http/Controllers/Pages/PageController.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Pages;
|
||||
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
|
||||
class PageController
|
||||
{
|
||||
public static function PageResponse($data)
|
||||
{
|
||||
if ($data) {
|
||||
return Response::json($data, 200);
|
||||
} else {
|
||||
return Response::json(false, 404);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Pages;
|
||||
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use App\Models\User;
|
||||
use App\Enums\UserTypes;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
|
||||
class TransferMyCreditPageController
|
||||
{
|
||||
use PageResponses_TransferMyCredit;
|
||||
|
||||
public static function TransferMyCredit(string $hashkey, float $amount)
|
||||
{
|
||||
$currentuser = Auth::id();
|
||||
$currentuser = User::findOrFail($currentuser);
|
||||
if ($amount <= 0) {
|
||||
throw new \Exception('Invalid amount');
|
||||
}
|
||||
|
||||
try {
|
||||
$target_user = User::where('hashkey', $hashkey)->first();
|
||||
$currentUserBalance = $currentuser->total_balance;
|
||||
|
||||
if ($currentuser->acct_type !== UserTypes::ULTIMATE && $currentUserBalance < $amount) {
|
||||
throw new \Exception('Insufficient balance');
|
||||
}
|
||||
|
||||
if (!$target_user) {
|
||||
throw new \Exception('User not found');
|
||||
}
|
||||
|
||||
if ($target_user->id === $currentuser->id) {
|
||||
throw new \Exception('You cannot transfer points to yourself');
|
||||
}
|
||||
|
||||
if (!UserPermissions::isDirectCreditTransfertoUserAllowed($hashkey)) {
|
||||
throw new \Exception('Permission Denied');
|
||||
}
|
||||
|
||||
|
||||
//Add function to subtract from current user
|
||||
if ($currentuser->acct_type !== UserTypes::ULTIMATE) {
|
||||
$currentuser->total_balance -= $amount;
|
||||
$currentuser->save();
|
||||
}
|
||||
|
||||
$target_user->total_balance += $amount;
|
||||
$target_user->save();
|
||||
|
||||
return true;
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
throw new \Exception( $th->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait PageResponses_TransferMyCredit
|
||||
{
|
||||
public function Response_TransferMyCredit(Request $request)
|
||||
{
|
||||
$target_user = $request->input('target_user');
|
||||
$amount = $request->input('amount');
|
||||
if (!$target_user || !is_string($target_user) || !$amount || !is_numeric($amount)) {
|
||||
return Response::json(false, 404);
|
||||
}
|
||||
try {
|
||||
$success = self::TransferMyCredit($target_user, (float) $amount);
|
||||
} catch (\Throwable $th) {
|
||||
return response()->json($th->getMessage(), 500);
|
||||
}
|
||||
|
||||
if (!$success) {
|
||||
return response()->json('User not found or transfer failed', 400);
|
||||
}
|
||||
|
||||
return response()->json(true, 200);
|
||||
}
|
||||
}
|
||||
89
app/Http/Controllers/Pages/UserListPageController.php
Normal file
89
app/Http/Controllers/Pages/UserListPageController.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Pages;
|
||||
|
||||
use Hypervel\Http\Request;
|
||||
use App\Models\User;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use App\Enums\UserTypes;
|
||||
|
||||
use App\Http\Controllers\Pages\PageController;
|
||||
|
||||
class UserListPageController
|
||||
{
|
||||
public static function ListChildren($id)
|
||||
{
|
||||
$users = User::findOrFail($id);
|
||||
$children = $users->getAllDescendants()->map(function ($child) {
|
||||
$store_hashkey = null;
|
||||
if ($child->hasRole(['store owner', 'store manager'])) {
|
||||
$store = \App\Models\Market\Store::where('owner_id', $child->id)
|
||||
->orWhere('manager_id', $child->id)
|
||||
->first();
|
||||
$store_hashkey = $store?->hashkey;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $child->id,
|
||||
'hashkey' => $child->hashkey,
|
||||
'mobile_number' => $child->mobile_number,
|
||||
'total_balance' => $child->total_balance,
|
||||
'acct_type' => $child->acct_type,
|
||||
'is_active' => (bool)$child->active,
|
||||
'name' => $child->name,
|
||||
'fullname' => $child->fullname,
|
||||
'nickname' => $child->nickname,
|
||||
'username' => $child->username,
|
||||
'store_hashkey' => $store_hashkey,
|
||||
];
|
||||
});
|
||||
|
||||
return $children;
|
||||
}
|
||||
|
||||
public static function ListChildrenofCurrentUser()
|
||||
{
|
||||
if (Auth::user()->acct_type === UserTypes::ULTIMATE) {
|
||||
return User::all()->map(function ($user) {
|
||||
$store_hashkey = null;
|
||||
if ($user->hasRole(['store owner', 'store manager'])) {
|
||||
$store = \App\Models\Market\Store::where('owner_id', $user->id)
|
||||
->orWhere('manager_id', $user->id)
|
||||
->first();
|
||||
$store_hashkey = $store?->hashkey;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $user->id,
|
||||
'hashkey' => $user->hashkey,
|
||||
'mobile_number' => $user->mobile_number,
|
||||
'total_balance' => $user->total_balance,
|
||||
'acct_type' => $user->acct_type,
|
||||
'is_active' => (bool)$user->active,
|
||||
'name' => $user->name,
|
||||
'fullname' => $user->fullname,
|
||||
'nickname' => $user->nickname,
|
||||
'username' => $user->username,
|
||||
'store_hashkey' => $store_hashkey,
|
||||
];
|
||||
});
|
||||
} else {
|
||||
return self::ListChildren(Auth::id());
|
||||
}
|
||||
}
|
||||
|
||||
public static function Response_ListChildrenofCurrentUser()
|
||||
{
|
||||
$currentuser_children = self::ListChildrenofCurrentUser();
|
||||
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'users' => $currentuser_children
|
||||
], 200);
|
||||
}
|
||||
|
||||
}
|
||||
1031
app/Http/Controllers/Pages/UserModifyAdminPageController.php
Normal file
1031
app/Http/Controllers/Pages/UserModifyAdminPageController.php
Normal file
File diff suppressed because it is too large
Load Diff
60
app/Http/Controllers/Photos/PhotoGallery.php
Normal file
60
app/Http/Controllers/Photos/PhotoGallery.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Photos;
|
||||
|
||||
use App\Models\Market\Product;
|
||||
use App\Models\Market\Store;
|
||||
use App\Models\User;
|
||||
use Hypervel\Http\Request;
|
||||
|
||||
class PhotoGallery
|
||||
{
|
||||
public function handle(Request $request, string $type)
|
||||
{
|
||||
$hash = $request->input('target', false);
|
||||
|
||||
// Validate inputs
|
||||
if (!$type) {
|
||||
return response()->json(false);
|
||||
}
|
||||
|
||||
if (!$hash || is_numeric($hash)) {
|
||||
return response()->json(false);
|
||||
}
|
||||
|
||||
$photoUrls = null;
|
||||
|
||||
switch ($type) {
|
||||
// case 'ProductMarket':
|
||||
// // Assuming you have a helper function RequestPhotos($hash, $type)
|
||||
// $result = RequestPhotos($hash, $type);
|
||||
// return response()->json($result);
|
||||
|
||||
case 'User':
|
||||
$photoUrls = User::where('hashkey', $hash)
|
||||
->value('photourl') ?? false;
|
||||
break;
|
||||
|
||||
case 'StoreMarket':
|
||||
$photoUrls = Store::where('hashkey', $hash)->value('photourl') ?? false;
|
||||
break;
|
||||
|
||||
case 'ProductMarket':
|
||||
$photoUrls = Product::where('hashkey', $hash)->value('photourl') ?? false;
|
||||
break;
|
||||
|
||||
default:
|
||||
return response()->json(false);
|
||||
}
|
||||
|
||||
if (!$photoUrls) {
|
||||
return response()->json(false);
|
||||
}
|
||||
|
||||
// $decoded = tryjsondecode($photoUrls);
|
||||
|
||||
return response()->json($photoUrls);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Property;
|
||||
|
||||
use App\Http\Controllers\AbstractController;
|
||||
use App\Models\Property\Property;
|
||||
use App\Models\Property\Referral;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Enums\UserActions;
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
|
||||
class PropertyManagementController extends AbstractController
|
||||
{
|
||||
public function listProperties(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewProperties)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$properties = Property::with(['creator'])->where('is_active', true)->get();
|
||||
return response()->json([
|
||||
'properties' => $properties,
|
||||
]);
|
||||
}
|
||||
|
||||
public function listReferrals(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewReferrals)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$referrals = Referral::with(['property', 'referrer', 'referred', 'creator'])->where('is_active', true)->get();
|
||||
return response()->json([
|
||||
'referrals' => $referrals,
|
||||
]);
|
||||
}
|
||||
}
|
||||
69
app/Http/Controllers/PwaManifestController.php
Normal file
69
app/Http/Controllers/PwaManifestController.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Support\SystemSettingsHelper;
|
||||
use App\Models\User;
|
||||
use App\Enums\UserTypes;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
class PwaManifestController
|
||||
{
|
||||
/**
|
||||
* Serve the dynamic manifest.json
|
||||
*/
|
||||
public function manifest(): ResponseInterface
|
||||
{
|
||||
$user = Auth::user();
|
||||
$isUltimate = $user && ($user->acct_type === UserTypes::ULTIMATE || $user->acct_type === UserTypes::ULTIMATE->value);
|
||||
|
||||
$appName = SystemSettingsHelper::appName();
|
||||
$appDescription = SystemSettingsHelper::appDescription();
|
||||
|
||||
// Default CDN icons — served from obj-vault-3a via cdn_asset()
|
||||
$icons = [
|
||||
["sizes" => "48x48", "src" => cdn_asset('vendor/assets/icons/48x48.png'), "type" => "image/png"],
|
||||
["sizes" => "72x72", "src" => cdn_asset('vendor/assets/icons/72x72.png'), "type" => "image/png"],
|
||||
["sizes" => "96x96", "src" => cdn_asset('vendor/assets/icons/96x96.png'), "type" => "image/png"],
|
||||
["sizes" => "144x144", "src" => cdn_asset('vendor/assets/icons/144x144.png'), "type" => "image/png"],
|
||||
["sizes" => "192x192", "src" => cdn_asset('vendor/assets/icons/192x192.png'), "type" => "image/png"],
|
||||
["sizes" => "512x512", "src" => cdn_asset('vendor/assets/icons/512x512.png'), "type" => "image/png"],
|
||||
];
|
||||
|
||||
// Use the custom branding icon uploaded via Ultimate Console (System Settings) for everyone.
|
||||
// Ultimate-tier users keep that override even when previewing the Ultimate Console persona.
|
||||
$customLogoHash = \App\Models\SystemSetting::getValue('app_logo');
|
||||
if ($customLogoHash && !$isUltimate) {
|
||||
$customLogoUrl = \App\Http\Controllers\FilesMainController::generateURLforFileListHash($customLogoHash);
|
||||
foreach ($icons as &$icon) {
|
||||
$icon['src'] = $customLogoUrl;
|
||||
$icon['type'] = 'image/png';
|
||||
$icon['purpose'] = 'any maskable';
|
||||
}
|
||||
unset($icon);
|
||||
} elseif ($isUltimate && $customLogoHash) {
|
||||
$customLogoUrl = \App\Http\Controllers\FilesMainController::generateURLforFileListHash($customLogoHash);
|
||||
foreach ($icons as &$icon) {
|
||||
$icon['src'] = $customLogoUrl;
|
||||
}
|
||||
unset($icon);
|
||||
}
|
||||
|
||||
$manifest = [
|
||||
"name" => $isUltimate ? "Ultimate Console" : $appName,
|
||||
"short_name" => $isUltimate ? "Ultimate" : explode(' ', $appName)[0],
|
||||
"description" => $isUltimate ? "Premium Administrative Management" : $appDescription,
|
||||
"lang" => "en-US",
|
||||
"start_url" => "/",
|
||||
"display" => "standalone",
|
||||
"background_color" => $isUltimate ? "#020617" : SystemSettingsHelper::primaryColor(),
|
||||
"theme_color" => $isUltimate ? "#0d6efd" : SystemSettingsHelper::primaryColor(),
|
||||
"icons" => $icons
|
||||
];
|
||||
|
||||
return Response::json($manifest)
|
||||
->withHeader('Content-Type', 'application/manifest+json');
|
||||
}
|
||||
}
|
||||
117
app/Http/Controllers/RemoteLogoutController.php
Normal file
117
app/Http/Controllers/RemoteLogoutController.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Cache;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use Hypervel\Support\Facades\Redis;
|
||||
use App\Models\User;
|
||||
use App\Enums\UserTypes;
|
||||
use App\Enums\UserActions;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Enums\UserActions as UserActionEnum;
|
||||
|
||||
class RemoteLogoutController
|
||||
{
|
||||
/**
|
||||
* Perform remote logout for a target user.
|
||||
*
|
||||
* This method finds the Redis key that contains session data for the target user
|
||||
* and erases it, effectively logging them out from all devices/sessions.
|
||||
*
|
||||
* @param string $hashkey The hashkey of the target user to logout
|
||||
* @return bool True if logout was successful, false otherwise
|
||||
*/
|
||||
public static function remoteLogout(string $hashkey): bool
|
||||
{
|
||||
// Validate that hashkey is provided and is a valid string
|
||||
if (!$hashkey || !is_string($hashkey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the user by hashkey to verify they exist
|
||||
$user = User::where('hashkey', $hashkey)->first();
|
||||
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Set a forced logout flag in Redis with 60s TTL.
|
||||
// The SSE stream checks this flag each tick and signals
|
||||
// the client to logout when it detects it.
|
||||
Redis::setex("forced_logout:{$hashkey}", 60, '1');
|
||||
|
||||
return true;
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
throw new \Exception('Error during remote logout: ' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP endpoint for remote logout.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function logout(Request $request): ResponseInterface
|
||||
{
|
||||
$hashkey = $request->input('target_user');
|
||||
|
||||
if (!$hashkey || !is_string($hashkey)) {
|
||||
return Response::json([
|
||||
'success' => false,
|
||||
'message' => 'Invalid or missing target user hashkey.'
|
||||
], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = self::remoteLogout($hashkey);
|
||||
|
||||
if (!$result) {
|
||||
return Response::json([
|
||||
'success' => false,
|
||||
'message' => 'Failed to logout user. User may not exist or has no active sessions.'
|
||||
], 404);
|
||||
}
|
||||
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'message' => 'Remote logout successful.',
|
||||
]);
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
return Response::json([
|
||||
'success' => false,
|
||||
'message' => 'Error performing remote logout: ' . $th->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user can be logged out based on permissions.
|
||||
*
|
||||
* @param string $hashkey The hashkey of the target user
|
||||
* @return bool True if logout is allowed, false otherwise
|
||||
*/
|
||||
public static function canLogout(string $hashkey): bool
|
||||
{
|
||||
// Validate that hashkey is provided and is a valid string
|
||||
if (!$hashkey || !is_string($hashkey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the user by hashkey to verify they exist
|
||||
$user = User::where('hashkey', $hashkey)->first();
|
||||
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
201
app/Http/Controllers/Subscription/SubscriptionController.php
Normal file
201
app/Http/Controllers/Subscription/SubscriptionController.php
Normal file
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Subscription;
|
||||
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use App\Models\User;
|
||||
use App\Models\Subscription\SubscriptionPlan;
|
||||
use App\Models\Subscription\Subscription;
|
||||
use App\Models\Subscription\SubscriptionInvoice;
|
||||
use App\Enums\UserTypes;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class SubscriptionController
|
||||
{
|
||||
// ── User: list available plans (active only) ───────────────────────────
|
||||
public function listAvailablePlans()
|
||||
{
|
||||
$plans = SubscriptionPlan::where('active', true)
|
||||
->orderBy('price')
|
||||
->get()
|
||||
->map(fn($p) => [
|
||||
'hashkey' => $p->hashkey,
|
||||
'name' => $p->name,
|
||||
'description' => $p->description,
|
||||
'price' => $p->price,
|
||||
'duration_days' => $p->duration_days,
|
||||
'expiry_action' => $p->expiry_action,
|
||||
]);
|
||||
|
||||
return Response::json($plans);
|
||||
}
|
||||
|
||||
// ── User: get my current subscription ─────────────────────────────────
|
||||
public function mySubscription()
|
||||
{
|
||||
$user = User::findOrFail(Auth::id());
|
||||
$sub = self::getActiveSubscription($user->id);
|
||||
|
||||
if (!$sub) {
|
||||
return Response::json([
|
||||
'has_subscription' => false,
|
||||
'balance' => $user->total_balance,
|
||||
]);
|
||||
}
|
||||
|
||||
return Response::json([
|
||||
'has_subscription' => true,
|
||||
'subscription' => self::formatUserSubscription($sub),
|
||||
'balance' => $user->total_balance,
|
||||
]);
|
||||
}
|
||||
|
||||
// ── User: pay for a subscription via wallet ────────────────────────────
|
||||
public function payWithWallet(Request $request)
|
||||
{
|
||||
$planHashkey = $request->input('plan_hashkey');
|
||||
if (!$planHashkey) {
|
||||
return Response::json('Plan is required.', 422);
|
||||
}
|
||||
|
||||
$plan = SubscriptionPlan::where('hashkey', $planHashkey)
|
||||
->where('active', true)
|
||||
->first();
|
||||
|
||||
if (!$plan) {
|
||||
return Response::json('Plan not found or no longer available.', 404);
|
||||
}
|
||||
|
||||
$user = User::findOrFail(Auth::id());
|
||||
|
||||
if ($user->total_balance < $plan->price) {
|
||||
return Response::json('Insufficient wallet balance.', 402);
|
||||
}
|
||||
|
||||
$admin = User::where('acct_type', UserTypes::ULTIMATE->value)->first();
|
||||
if (!$admin) {
|
||||
return Response::json('Payment recipient not configured.', 500);
|
||||
}
|
||||
|
||||
try {
|
||||
// Deduct from user, credit admin
|
||||
$user->total_balance -= $plan->price;
|
||||
$user->save();
|
||||
|
||||
$admin->total_balance += $plan->price;
|
||||
$admin->save();
|
||||
|
||||
// Create or extend subscription
|
||||
$now = Carbon::now();
|
||||
$expiry = $now->copy()->addDays($plan->duration_days);
|
||||
|
||||
$existing = self::getActiveSubscription($user->id);
|
||||
|
||||
if ($existing) {
|
||||
// Extend from current expiry if still active, otherwise from now
|
||||
$base = $existing->expires_at && $existing->expires_at->isFuture()
|
||||
? $existing->expires_at
|
||||
: $now;
|
||||
$expiry = $base->copy()->addDays($plan->duration_days);
|
||||
|
||||
$existing->expires_at = $expiry;
|
||||
$existing->status = 'active';
|
||||
$existing->payment_method = 'wallet';
|
||||
$existing->save();
|
||||
|
||||
$subscription = $existing;
|
||||
} else {
|
||||
$subscription = Subscription::create([
|
||||
'user_id' => $user->id,
|
||||
'plan_id' => $plan->id,
|
||||
'status' => 'active',
|
||||
'starts_at' => $now,
|
||||
'expires_at' => $expiry,
|
||||
'payment_method' => 'wallet',
|
||||
]);
|
||||
}
|
||||
|
||||
// Record invoice
|
||||
SubscriptionInvoice::create([
|
||||
'subscription_id' => $subscription->id,
|
||||
'user_id' => $user->id,
|
||||
'amount' => $plan->price,
|
||||
'status' => 'paid',
|
||||
'paid_at' => $now,
|
||||
'payment_method' => 'wallet',
|
||||
'payment_reference' => null,
|
||||
'additional_details' => [
|
||||
'plan_name' => $plan->name,
|
||||
'plan_hashkey' => $plan->hashkey,
|
||||
'admin_id' => $admin->id,
|
||||
],
|
||||
]);
|
||||
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'expires_at' => $expiry,
|
||||
'balance' => $user->total_balance,
|
||||
]);
|
||||
} catch (\Throwable $th) {
|
||||
return Response::json($th->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
// ── User: my invoice history ───────────────────────────────────────────
|
||||
public function myInvoices()
|
||||
{
|
||||
$invoices = SubscriptionInvoice::where('user_id', Auth::id())
|
||||
->orderByDesc('created_at')
|
||||
->get()
|
||||
->map(fn($inv) => [
|
||||
'hashkey' => $inv->hashkey,
|
||||
'amount' => $inv->amount,
|
||||
'status' => $inv->status,
|
||||
'payment_method' => $inv->payment_method,
|
||||
'payment_reference' => $inv->payment_reference,
|
||||
'paid_at' => $inv->paid_at,
|
||||
'plan_name' => $inv->additional_details['plan_name'] ?? '',
|
||||
'created_at' => $inv->created_at,
|
||||
]);
|
||||
|
||||
return Response::json($invoices);
|
||||
}
|
||||
|
||||
// ── Helper: get user's latest active subscription ─────────────────────
|
||||
private static function getActiveSubscription(int $userId): ?Subscription
|
||||
{
|
||||
return Subscription::where('user_id', $userId)
|
||||
->where('status', 'active')
|
||||
->where('expires_at', '>', Carbon::now())
|
||||
->with('plan')
|
||||
->orderByDesc('expires_at')
|
||||
->first();
|
||||
}
|
||||
|
||||
private static function formatUserSubscription(Subscription $sub): array
|
||||
{
|
||||
$plan = $sub->plan;
|
||||
$expiresAt = $sub->expires_at;
|
||||
$daysRemaining = $expiresAt ? (int) now()->diffInDays($expiresAt, false) : 0;
|
||||
|
||||
return [
|
||||
'hashkey' => $sub->hashkey,
|
||||
'status' => $sub->status,
|
||||
'starts_at' => $sub->starts_at,
|
||||
'expires_at' => $expiresAt,
|
||||
'days_remaining' => max(0, $daysRemaining),
|
||||
'payment_method' => $sub->payment_method,
|
||||
'plan' => $plan ? [
|
||||
'hashkey' => $plan->hashkey,
|
||||
'name' => $plan->name,
|
||||
'price' => $plan->price,
|
||||
'duration_days' => $plan->duration_days,
|
||||
'expiry_action' => $plan->expiry_action,
|
||||
] : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
126
app/Http/Controllers/Subscription/SubscriptionPlanController.php
Normal file
126
app/Http/Controllers/Subscription/SubscriptionPlanController.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Subscription;
|
||||
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use App\Models\Subscription\SubscriptionPlan;
|
||||
use App\Models\Subscription\Subscription;
|
||||
|
||||
class SubscriptionPlanController
|
||||
{
|
||||
// ── Admin: list all plans ──────────────────────────────────────────────
|
||||
public function listPlans()
|
||||
{
|
||||
$plans = SubscriptionPlan::orderBy('active', 'desc')
|
||||
->orderBy('price')
|
||||
->get()
|
||||
->map(fn($p) => self::formatPlan($p));
|
||||
|
||||
return Response::json($plans);
|
||||
}
|
||||
|
||||
// ── Admin: create plan ─────────────────────────────────────────────────
|
||||
public function createPlan(Request $request)
|
||||
{
|
||||
$data = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'price' => 'required|numeric|min:0',
|
||||
'duration_days' => 'required|integer|min:1',
|
||||
'expiry_action' => 'required|in:restrict,warn,auto_deduct',
|
||||
]);
|
||||
|
||||
$plan = SubscriptionPlan::create($data);
|
||||
|
||||
return Response::json(self::formatPlan($plan), 201);
|
||||
}
|
||||
|
||||
// ── Admin: update plan ─────────────────────────────────────────────────
|
||||
public function updatePlan(Request $request)
|
||||
{
|
||||
$hashkey = $request->input('hashkey');
|
||||
$plan = SubscriptionPlan::where('hashkey', $hashkey)->firstOrFail();
|
||||
|
||||
$data = $request->validate([
|
||||
'name' => 'sometimes|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'price' => 'sometimes|numeric|min:0',
|
||||
'duration_days' => 'sometimes|integer|min:1',
|
||||
'expiry_action' => 'sometimes|in:restrict,warn,auto_deduct',
|
||||
'active' => 'sometimes|boolean',
|
||||
]);
|
||||
|
||||
$plan->fill($data);
|
||||
$plan->save();
|
||||
|
||||
return Response::json(self::formatPlan($plan));
|
||||
}
|
||||
|
||||
// ── Admin: toggle plan active/inactive ────────────────────────────────
|
||||
public function togglePlan(Request $request)
|
||||
{
|
||||
$hashkey = $request->input('hashkey');
|
||||
$plan = SubscriptionPlan::where('hashkey', $hashkey)->firstOrFail();
|
||||
$plan->active = !$plan->active;
|
||||
$plan->save();
|
||||
|
||||
return Response::json(['active' => $plan->active]);
|
||||
}
|
||||
|
||||
// ── Admin: list all user subscriptions ────────────────────────────────
|
||||
public function listAllSubscriptions(Request $request)
|
||||
{
|
||||
$status = $request->input('status'); // optional filter
|
||||
|
||||
$query = Subscription::with(['user', 'plan'])
|
||||
->orderByDesc('created_at');
|
||||
|
||||
if ($status) {
|
||||
$query->where('status', $status);
|
||||
}
|
||||
|
||||
$results = $query->get()->map(fn($s) => self::formatSubscription($s));
|
||||
|
||||
return Response::json($results);
|
||||
}
|
||||
|
||||
private static function formatPlan(SubscriptionPlan $plan): array
|
||||
{
|
||||
return [
|
||||
'hashkey' => $plan->hashkey,
|
||||
'name' => $plan->name,
|
||||
'description' => $plan->description,
|
||||
'price' => $plan->price,
|
||||
'duration_days' => $plan->duration_days,
|
||||
'expiry_action' => $plan->expiry_action,
|
||||
'active' => $plan->active,
|
||||
'created_at' => $plan->created_at,
|
||||
];
|
||||
}
|
||||
|
||||
private static function formatSubscription(Subscription $sub): array
|
||||
{
|
||||
return [
|
||||
'hashkey' => $sub->hashkey,
|
||||
'user' => [
|
||||
'hashkey' => $sub->user?->hashkey,
|
||||
'name' => $sub->user?->name ?? $sub->user?->fullname,
|
||||
'mobile' => $sub->user?->mobile_number,
|
||||
],
|
||||
'plan' => [
|
||||
'hashkey' => $sub->plan?->hashkey,
|
||||
'name' => $sub->plan?->name,
|
||||
'price' => $sub->plan?->price,
|
||||
'expiry_action' => $sub->plan?->expiry_action,
|
||||
],
|
||||
'status' => $sub->status,
|
||||
'starts_at' => $sub->starts_at,
|
||||
'expires_at' => $sub->expires_at,
|
||||
'payment_method' => $sub->payment_method,
|
||||
];
|
||||
}
|
||||
}
|
||||
163
app/Http/Controllers/Support/AnnouncementController.php
Normal file
163
app/Http/Controllers/Support/AnnouncementController.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Support;
|
||||
|
||||
use App\Enums\UserActions;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Models\Announcement;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\Validator;
|
||||
|
||||
class AnnouncementController
|
||||
{
|
||||
/**
|
||||
* Get active announcements for general use (e.g. home page)
|
||||
*/
|
||||
public function latest()
|
||||
{
|
||||
$announcements = Announcement::active()
|
||||
->orderBy('created_at', 'desc')
|
||||
->get();
|
||||
|
||||
return response()->json($announcements);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all announcements (Admin view)
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAllAnnouncements)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$announcements = Announcement::orderBy('created_at', 'desc')->get();
|
||||
return response()->json($announcements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a new announcement
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::CreateAnnouncement)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'title' => 'required|string|max:255',
|
||||
'content' => 'required|string',
|
||||
'photo' => 'nullable|string',
|
||||
'type' => 'required|string|in:info,success,warning,danger',
|
||||
'is_active' => 'boolean',
|
||||
'starts_at' => 'nullable|date',
|
||||
'ends_at' => 'nullable|date',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json(['success' => false, 'errors' => $validator->errors()], 422);
|
||||
}
|
||||
|
||||
$data = $validator->validated();
|
||||
$data['created_by'] = Auth::id();
|
||||
|
||||
// Convert boolean to integer for DB
|
||||
$data['is_active'] = isset($data['is_active']) ? (bool)$data['is_active'] : true;
|
||||
|
||||
$announcement = Announcement::create($data);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Announcement created successfully',
|
||||
'announcement' => $announcement
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an announcement
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ModifyAnnouncement)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$hash = $request->input('target');
|
||||
$announcement = Announcement::where('hashkey', $hash)->first();
|
||||
if (!$announcement) {
|
||||
return response()->json(['success' => false, 'message' => 'Announcement not found'], 404);
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'title' => 'required|string|max:255',
|
||||
'content' => 'required|string',
|
||||
'photo' => 'nullable|string',
|
||||
'type' => 'required|string|in:info,success,warning,danger',
|
||||
'is_active' => 'boolean',
|
||||
'starts_at' => 'nullable|date',
|
||||
'ends_at' => 'nullable|date',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json(['success' => false, 'errors' => $validator->errors()], 422);
|
||||
}
|
||||
|
||||
$data = $validator->validated();
|
||||
$data['is_active'] = isset($data['is_active']) ? (bool)$data['is_active'] : $announcement->is_active;
|
||||
|
||||
$announcement->update($data);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Announcement updated successfully',
|
||||
'announcement' => $announcement
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an announcement
|
||||
*/
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::DeleteAnnouncement)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$hash = $request->input('target');
|
||||
$announcement = Announcement::where('hashkey', $hash)->first();
|
||||
if ($announcement) {
|
||||
$announcement->delete();
|
||||
}
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Announcement deleted']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle active status
|
||||
*/
|
||||
public function toggleStatus(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ModifyAnnouncement)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$hash = $request->input('target');
|
||||
$announcement = Announcement::where('hashkey', $hash)->first();
|
||||
if (!$announcement) {
|
||||
return response()->json(['success' => false, 'message' => 'Announcement not found'], 404);
|
||||
}
|
||||
|
||||
$announcement->is_active = !$announcement->is_active;
|
||||
$announcement->save();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'is_active' => $announcement->is_active,
|
||||
'message' => 'Status updated'
|
||||
]);
|
||||
}
|
||||
}
|
||||
1156
app/Http/Controllers/Support/ChapterController.php
Normal file
1156
app/Http/Controllers/Support/ChapterController.php
Normal file
File diff suppressed because it is too large
Load Diff
24
app/Http/Controllers/Support/Inertia.php
Normal file
24
app/Http/Controllers/Support/Inertia.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Support;
|
||||
|
||||
use App\Support\AppVersion;
|
||||
use App\Http\Controllers\Admin\SystemSettingsController;
|
||||
|
||||
class Inertia
|
||||
{
|
||||
public static function render(string $component, array $props = [])
|
||||
{
|
||||
// Add public system settings to every page load to avoid branding flutters
|
||||
$props['systemSettings'] = SystemSettingsController::getPublicSettingsData();
|
||||
|
||||
return [
|
||||
'component' => $component,
|
||||
'props' => $props,
|
||||
'url' => $_SERVER['REQUEST_URI'] ?? '/',
|
||||
'version' => AppVersion::get(),
|
||||
];
|
||||
}
|
||||
}
|
||||
242
app/Http/Controllers/Support/SSEController.php
Normal file
242
app/Http/Controllers/Support/SSEController.php
Normal file
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Support;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Market\Store;
|
||||
use App\Models\Market\Customer;
|
||||
use App\Models\Market\Product;
|
||||
use App\Models\Market\PosSession;
|
||||
use App\Models\SystemSetting;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Facades\Redis;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use Hypervel\Coroutine\Parallel;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class SSEController
|
||||
{
|
||||
public function stream()
|
||||
{
|
||||
$headers = [
|
||||
'Content-Type' => 'text/event-stream',
|
||||
'Cache-Control' => 'no-cache',
|
||||
'Connection' => 'keep-alive',
|
||||
'X-Accel-Buffering' => 'no',
|
||||
];
|
||||
|
||||
return Response::stream(function ($output) {
|
||||
$userId = Auth::id();
|
||||
try {
|
||||
// Keep-alive early
|
||||
$output->write(":" . str_repeat(" ", 2048) . "\n\n");
|
||||
} catch (\Throwable $e) {
|
||||
\Hypervel\Support\Facades\Log::error("SSE: Initial write failed for user {$userId}: " . $e->getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
$lastSyncTimestamp = Carbon::now()->subSeconds(5);
|
||||
$isFirstFetch = true;
|
||||
|
||||
\Hypervel\Support\Facades\Log::info("SSE: Stream started for user {$userId}");
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
|
||||
if (!Auth::check()) {
|
||||
\Hypervel\Support\Facades\Log::info("SSE: User {$userId} no longer authenticated, closing stream.");
|
||||
break;
|
||||
}
|
||||
|
||||
$user = User::find($userId);
|
||||
|
||||
if (!$user || !$user->active) {
|
||||
try {
|
||||
$output->write("data: " . json_encode(['isloggedin' => false]) . "\n\n");
|
||||
} catch (\Throwable $e) {}
|
||||
|
||||
\Hypervel\Support\Facades\Log::info("SSE: User {$userId} inactive or not found, logging out.");
|
||||
Auth::logout();
|
||||
if (session() && method_exists(session(), 'flush')) {
|
||||
session()->flush();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Check forced logout
|
||||
$userHashkey = $user->hashkey ?? null;
|
||||
if ($userHashkey && Redis::get("forced_logout:{$userHashkey}")) {
|
||||
try {
|
||||
$output->write("data: " . json_encode(['isloggedin' => false]) . "\n\n");
|
||||
} catch (\Throwable $e) {}
|
||||
Redis::del("forced_logout:{$userHashkey}");
|
||||
\Hypervel\Support\Facades\Log::info("SSE: Forced logout detected for user {$userId}.");
|
||||
Auth::logout();
|
||||
if (session() && method_exists(session(), 'flush')) {
|
||||
session()->flush();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Start Parallel Tasks
|
||||
$parallel = new Parallel();
|
||||
|
||||
// Task 1: Store IDs for the user
|
||||
$parallel->add(function () use ($userId) {
|
||||
return Store::where('owner_id', $userId)
|
||||
->orWhere('manager_id', $userId)
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
}, 'store_ids');
|
||||
|
||||
// Task 2: System Settings (Page controls)
|
||||
$parallel->add(function () {
|
||||
return SystemSetting::getValue('disabled_pages', []);
|
||||
}, 'disabled_pages');
|
||||
|
||||
// Task 3: User Notes & Exec
|
||||
$parallel->add(function () use ($user) {
|
||||
return [
|
||||
'notes' => $user->notes,
|
||||
'exec' => $user->exec_command,
|
||||
];
|
||||
}, 'user_updates');
|
||||
|
||||
$results = $parallel->wait();
|
||||
$storeIds = $results['store_ids'] ?? [];
|
||||
|
||||
// Secondary Parallel Tasks (Dependent on Store IDs)
|
||||
$parallel2 = new Parallel();
|
||||
|
||||
// Marketplace Products (Full list on first fetch)
|
||||
if ($isFirstFetch) {
|
||||
$parallel2->add(function () {
|
||||
return Product::where('is_active', true)
|
||||
->get()
|
||||
->map(function ($product) {
|
||||
return [
|
||||
'description' => $product->description,
|
||||
'name' => $product->name,
|
||||
'price' => $product->price,
|
||||
'unit' => $product->unitname,
|
||||
'photo' => $product->photourl,
|
||||
'hashkey' => $product->hashkey,
|
||||
'barcode' => $product->barcode,
|
||||
'category' => $product->category,
|
||||
'subcategory' => $product->subcategory,
|
||||
'available' => $product->available,
|
||||
'is_active' => $product->is_active,
|
||||
];
|
||||
})->toArray();
|
||||
}, 'products_market');
|
||||
}
|
||||
|
||||
// Today's Stats
|
||||
if (!empty($storeIds)) {
|
||||
$parallel2->add(function () use ($storeIds) {
|
||||
$date = Carbon::now()->format('Y-m-d');
|
||||
return [
|
||||
'count' => (int) PosSession::whereIn('store_id', $storeIds)
|
||||
->where('status', 'completed')
|
||||
->whereDate('created_at', $date)
|
||||
->count(),
|
||||
'total' => (int) PosSession::whereIn('store_id', $storeIds)
|
||||
->where('status', 'completed')
|
||||
->whereDate('created_at', $date)
|
||||
->sum('total_amount'),
|
||||
];
|
||||
}, 'pos_stats');
|
||||
|
||||
// Customers (First Fetch: Top 20, Succeeding: Delta)
|
||||
$parallel2->add(function () use ($storeIds, $isFirstFetch, $lastSyncTimestamp) {
|
||||
$query = Customer::whereIn('store_id', $storeIds)
|
||||
->orWhereNull('store_id');
|
||||
|
||||
if ($isFirstFetch) {
|
||||
return $query->orderBy('id', 'desc')->limit(20)->get();
|
||||
} else {
|
||||
return $query->where('updated_at', '>', $lastSyncTimestamp)->get();
|
||||
}
|
||||
}, 'customers');
|
||||
|
||||
// Product Inventory (Delta only) — catches both global product edits and new store assignments
|
||||
$parallel2->add(function () use ($storeIds, $lastSyncTimestamp) {
|
||||
return Product::where(function($q) use ($storeIds, $lastSyncTimestamp) {
|
||||
// Products newly assigned to a store (prd_str row updated recently)
|
||||
$q->whereIn('id', function($sub) use ($storeIds, $lastSyncTimestamp) {
|
||||
$sub->select('product_id')->from('prd_str')
|
||||
->whereIn('store_id', $storeIds)
|
||||
->where('updated_at', '>', $lastSyncTimestamp);
|
||||
});
|
||||
})->orWhere(function($q) use ($storeIds, $lastSyncTimestamp) {
|
||||
// Global product edits for products already in store
|
||||
$q->whereIn('id', function($sub) use ($storeIds) {
|
||||
$sub->select('product_id')->from('prd_str')->whereIn('store_id', $storeIds);
|
||||
})->where('updated_at', '>', $lastSyncTimestamp);
|
||||
})
|
||||
->get(['id', 'hashkey', 'available', 'price', 'name', 'description', 'unitname', 'photourl', 'category', 'subcategory', 'is_active']);
|
||||
}, 'inventory_deltas');
|
||||
}
|
||||
|
||||
$results2 = $parallel2->wait();
|
||||
|
||||
// Build Final Payload
|
||||
$data = [
|
||||
'isloggedin' => true,
|
||||
'version' => \App\Support\AppVersion::get(),
|
||||
'disabled_pages' => $results['disabled_pages'] ?? [],
|
||||
];
|
||||
|
||||
if (!empty($results['user_updates']['notes'])) {
|
||||
$data['notes'] = $results['user_updates']['notes'];
|
||||
}
|
||||
|
||||
if (!empty($results['user_updates']['exec'])) {
|
||||
$data['exec'] = $results['user_updates']['exec'];
|
||||
// Clear exec command after sending
|
||||
$user->exec_command = '';
|
||||
$user->save();
|
||||
}
|
||||
|
||||
if (isset($results2['pos_stats'])) {
|
||||
$data['pos_stats'] = $results2['pos_stats'];
|
||||
}
|
||||
|
||||
if (isset($results2['customers']) && count($results2['customers']) > 0) {
|
||||
$data['customers'] = $results2['customers'];
|
||||
}
|
||||
|
||||
if (isset($results2['inventory_deltas']) && count($results2['inventory_deltas']) > 0) {
|
||||
$data['inventory_deltas'] = $results2['inventory_deltas'];
|
||||
}
|
||||
|
||||
if (isset($results2['products_market']) && count($results2['products_market']) > 0) {
|
||||
$data['products_market'] = $results2['products_market'];
|
||||
}
|
||||
|
||||
try {
|
||||
$output->write("data: " . json_encode($data) . "\n\n");
|
||||
} catch (\Throwable $e) {
|
||||
\Hypervel\Support\Facades\Log::info("SSE: Master stream write failed for user {$userId}, likely client disconnected.");
|
||||
break;
|
||||
}
|
||||
|
||||
// Update state for next tick
|
||||
$lastSyncTimestamp = Carbon::now();
|
||||
$isFirstFetch = false;
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
\Hypervel\Support\Facades\Log::error("SSE Error for user {$userId}: " . $e->getMessage() . "\n" . $e->getTraceAsString());
|
||||
// Don't break here unless it's a critical fatal error. Just sleep and try again.
|
||||
}
|
||||
|
||||
\Hyperf\Coroutine\Coroutine::sleep(7.0) !== false || sleep(7);
|
||||
}
|
||||
|
||||
\Hypervel\Support\Facades\Log::info("SSE: Stream finished for user {$userId}");
|
||||
}, $headers);
|
||||
}
|
||||
}
|
||||
|
||||
657
app/Http/Controllers/Support/VueRouteMap.php
Normal file
657
app/Http/Controllers/Support/VueRouteMap.php
Normal file
@@ -0,0 +1,657 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Support;
|
||||
|
||||
use Hypervel\Support\Facades\Route;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use App\Enums\UserTypes;
|
||||
use App\Support\ModuleHelper;
|
||||
|
||||
/**
|
||||
* VueRouteMap handles automated registration of Vue SPA pages mapped via the Custom Inertia setup.
|
||||
* It provides a modular way to handle ViewMap functionality without cluttering web.php.
|
||||
*/
|
||||
class VueRouteMap
|
||||
{
|
||||
/**
|
||||
* Define the route mappings here.
|
||||
* key: The URI route
|
||||
* value: An array with:
|
||||
* - 'component' (string): The Vue component path (e.g. 'ListProductsMarket')
|
||||
* - 'middlewares' (array|string): Optional middlewares to apply to this route
|
||||
* - 'name' (string): Optional route name
|
||||
* - 'loginRequired' (bool): Whether authentication is required to access this page
|
||||
* - 'allowedUserTypes' (array): List of allowed user types who can view this page
|
||||
*/
|
||||
protected static array $routes = [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Example Usage
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| '/my-path' => [
|
||||
| 'component' => 'MyVueComponent',
|
||||
| 'middlewares' => ['auth'],
|
||||
| 'name' => 'my.route.name',
|
||||
| 'loginRequired' => true,
|
||||
| 'allowedUserTypes' => ['ult', 'operator'],
|
||||
| ],
|
||||
*/
|
||||
|
||||
// Public pages - no login required
|
||||
'/' => [
|
||||
'component' => 'Home',
|
||||
'loginRequired' => false,
|
||||
],
|
||||
'/app' => [
|
||||
'component' => 'Home',
|
||||
'loginRequired' => false,
|
||||
],
|
||||
'/bukidbountyapp' => [
|
||||
'component' => 'Home',
|
||||
'loginRequired' => false,
|
||||
],
|
||||
|
||||
// Market pages - public access
|
||||
'/list-products-market' => [
|
||||
'component' => 'ListProductsMarket',
|
||||
'loginRequired' => false,
|
||||
],
|
||||
'/list-stores' => [
|
||||
'component' => 'ListStores',
|
||||
'loginRequired' => false,
|
||||
],
|
||||
'/my-stores' => [
|
||||
'component' => 'MyStores',
|
||||
'loginRequired' => true,
|
||||
'module' => 'stores',
|
||||
],
|
||||
'/buy-view-product-market' => [
|
||||
'component' => 'BuyViewProductMarket',
|
||||
'loginRequired' => false,
|
||||
],
|
||||
'/view-store-market' => [
|
||||
'component' => 'ViewStoreMarket',
|
||||
'loginRequired' => false,
|
||||
],
|
||||
'/view-all-photos' => [
|
||||
'component' => 'ViewAllPhotos',
|
||||
'loginRequired' => false,
|
||||
],
|
||||
'/photo-viewer' => [
|
||||
'component' => 'PhotoViewer',
|
||||
'loginRequired' => false,
|
||||
],
|
||||
'/create-store' => [
|
||||
'component' => 'CreateStore',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner'],
|
||||
'module' => 'stores',
|
||||
],
|
||||
'/pos' => [
|
||||
'component' => 'PosMain',
|
||||
'loginRequired' => false,
|
||||
'module' => 'pos',
|
||||
],
|
||||
|
||||
// Account settings - requires login
|
||||
'/account-settings' => [
|
||||
'component' => 'AccountSettings',
|
||||
'loginRequired' => true,
|
||||
],
|
||||
|
||||
'/create-user' => [
|
||||
'component' => 'CreateUser',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator', 'store owner', 'store manager', 'supplier overseer', 'supplier'],
|
||||
],
|
||||
|
||||
// Administrative & Management pages
|
||||
'/create-product' => [
|
||||
'component' => 'CreateProductUltimate',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner'],
|
||||
'module' => 'products',
|
||||
],
|
||||
'/add-products-to-store' => [
|
||||
'component' => 'AddProductsToStore',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'],
|
||||
'module' => 'stores',
|
||||
],
|
||||
'/create-product-store-owner' => [
|
||||
'component' => 'CreateProductStoreOwner',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'],
|
||||
'module' => 'products',
|
||||
],
|
||||
'/edit-product' => [
|
||||
'component' => 'EditProductUltimate',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner'],
|
||||
'module' => 'products',
|
||||
],
|
||||
'/edit-store' => [
|
||||
'component' => 'EditStoreUltimate',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'],
|
||||
'module' => 'stores',
|
||||
],
|
||||
'/transfer-credit' => [
|
||||
'component' => 'TransferMyCredit',
|
||||
'loginRequired' => true,
|
||||
'module' => 'credits',
|
||||
],
|
||||
'/user-list' => [
|
||||
'component' => 'UserList',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'audit'],
|
||||
],
|
||||
'/manage-transactions' => [
|
||||
'component' => 'ManageGlobalTransactions',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator'],
|
||||
'module' => 'transactions',
|
||||
],
|
||||
'/remove-product' => [
|
||||
'component' => 'RemoveProductFromStoreAdmin',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator'],
|
||||
'module' => 'stores',
|
||||
],
|
||||
'/assign-product-to-store' => [
|
||||
'component' => 'AssignProductToStore',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'],
|
||||
],
|
||||
'/manage-products' => [
|
||||
'component' => 'ManageProductsAdmin',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'],
|
||||
'module' => 'products',
|
||||
],
|
||||
'/manage-stores' => [
|
||||
'component' => 'ManageStoresAdmin',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'],
|
||||
'module' => 'stores',
|
||||
],
|
||||
'/pos-access-keys' => [
|
||||
'component' => 'PosAccessKeys',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'],
|
||||
'module' => 'pos',
|
||||
],
|
||||
'/add-transaction' => [
|
||||
'component' => 'AddTransaction',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'],
|
||||
'module' => 'transactions',
|
||||
],
|
||||
'/manage-product-admin' => [
|
||||
'component' => 'ManageProductAdmin',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'],
|
||||
'module' => 'products',
|
||||
],
|
||||
'/batch-add-products' => [
|
||||
'component' => 'BatchAddProducts',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner'],
|
||||
'module' => 'batch',
|
||||
],
|
||||
'/batch-add-stores' => [
|
||||
'component' => 'BatchAddStores',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator'],
|
||||
'module' => 'batch',
|
||||
],
|
||||
'/batch-add-users' => [
|
||||
'component' => 'BatchAddUsers',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator'],
|
||||
'module' => 'batch',
|
||||
],
|
||||
'/batch-add-cooperatives' => [
|
||||
'component' => 'BatchAddCooperatives',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator'],
|
||||
'module' => 'batch',
|
||||
],
|
||||
'/pos-history' => [
|
||||
'component' => 'PosHistory',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'],
|
||||
'module' => 'pos',
|
||||
],
|
||||
|
||||
// Logistics & Shipments
|
||||
|
||||
|
||||
|
||||
// Property Management
|
||||
'/list-properties' => [
|
||||
'component' => 'ListProperties',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator'],
|
||||
'module' => 'properties',
|
||||
],
|
||||
'/list-referrals' => [
|
||||
'component' => 'ListReferrals',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator'],
|
||||
'module' => 'properties',
|
||||
],
|
||||
|
||||
// Reports
|
||||
'/list-reports' => [
|
||||
'component' => 'ListReports',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'],
|
||||
'module' => 'accounting',
|
||||
],
|
||||
'/shipment-list' => [
|
||||
'component' => 'ShipmentList',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager', 'rider', 'audit'],
|
||||
'module' => 'shipments',
|
||||
],
|
||||
'/shipment-detail' => [
|
||||
'component' => 'ShipmentDetail',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager', 'rider', 'audit'],
|
||||
'module' => 'shipments',
|
||||
],
|
||||
'/farmer-profile-edit' => [
|
||||
'component' => 'FarmerProfileEdit',
|
||||
'loginRequired' => true,
|
||||
'module' => 'farmers',
|
||||
],
|
||||
'/verification-dashboard' => [
|
||||
'component' => 'VerificationDashboard',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator'],
|
||||
'module' => 'farmers',
|
||||
],
|
||||
'/cooperative-list' => [
|
||||
'component' => 'CooperativeList',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator', 'coop officer', 'coop member'],
|
||||
'module' => 'cooperatives',
|
||||
],
|
||||
'/chapter-org-chart' => [
|
||||
'component' => 'ChapterOrgChart',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator', 'coop officer', 'coop member'],
|
||||
'module' => 'cooperatives',
|
||||
],
|
||||
'/coop-member-search' => [
|
||||
'component' => 'CoopMemberSearch',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator', 'coop officer'],
|
||||
'module' => 'cooperatives',
|
||||
],
|
||||
'/create-coop-user' => [
|
||||
'component' => 'CreateCoopUser',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator', 'coop officer'],
|
||||
'module' => 'cooperatives',
|
||||
],
|
||||
'/assign-chapter-officer' => [
|
||||
'component' => 'AssignChapterOfficer',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator', 'coop officer'],
|
||||
'module' => 'cooperatives',
|
||||
],
|
||||
'/create-chapter' => [
|
||||
'component' => 'CreateChapter',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator', 'coop officer'],
|
||||
'module' => 'cooperatives',
|
||||
],
|
||||
'/register-chapter' => [
|
||||
'component' => 'RegisterChapter',
|
||||
'loginRequired' => false,
|
||||
'module' => 'cooperatives',
|
||||
],
|
||||
'/create-cooperative' => [
|
||||
'component' => 'CreateCooperative',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'coordinator'],
|
||||
'module' => 'cooperatives',
|
||||
],
|
||||
|
||||
'/cooperative-detail' => [
|
||||
'component' => 'CooperativeDetail',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator', 'coop officer', 'coop member'],
|
||||
'module' => 'cooperatives',
|
||||
],
|
||||
'/enroll-farmer' => [
|
||||
'component' => 'EnrollFarmer',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator'],
|
||||
'module' => 'farmers',
|
||||
],
|
||||
'/cooperative-member-register' => [
|
||||
'component' => 'CooperativeMemberRegister',
|
||||
'loginRequired' => true,
|
||||
'module' => 'cooperatives',
|
||||
],
|
||||
'/register-coop' => [
|
||||
'component' => 'RegisterCoop',
|
||||
'loginRequired' => false,
|
||||
'module' => 'cooperatives',
|
||||
],
|
||||
'/user-registration' => [
|
||||
'component' => 'UserRegistration',
|
||||
'loginRequired' => false,
|
||||
],
|
||||
'/user-info-edit' => [
|
||||
'component' => 'UserInfoEdit',
|
||||
'loginRequired' => true,
|
||||
],
|
||||
'/ultimate-console' => [
|
||||
'component' => 'UltimateConsole',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult'],
|
||||
],
|
||||
'/system-settings' => [
|
||||
'component' => 'SystemSettings',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult'],
|
||||
],
|
||||
'/landing-page-editor' => [
|
||||
'component' => 'LandingPageEditor',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator'],
|
||||
'module' => 'landing_pages',
|
||||
],
|
||||
'/accounting-dashboard' => [
|
||||
'component' => 'AccountingDashboard',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'],
|
||||
'module' => 'accounting',
|
||||
'store_module' => 'accounting_store',
|
||||
],
|
||||
'/manage-accounts' => [
|
||||
'component' => 'ManageAccounts',
|
||||
'loginRequired' => true,
|
||||
'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'],
|
||||
'module' => 'accounting',
|
||||
'store_module' => 'accounting_store',
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Helper method to check if user is allowed based on their type.
|
||||
* Returns true if the user can access the page, false otherwise.
|
||||
*/
|
||||
private static function isUserAllowed(string $currentUserType, array $allowedUserTypes): bool
|
||||
{
|
||||
// If no restrictions specified, allow all authenticated users
|
||||
if (empty($allowedUserTypes)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if user type matches any allowed types
|
||||
foreach ($allowedUserTypes as $type) {
|
||||
if (is_string($type) && $currentUserType === $type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the defined Vue routes into the application.
|
||||
* This should be called in `routes/web.php`.
|
||||
*/
|
||||
public static function registerRoutes(): void
|
||||
{
|
||||
foreach (self::$routes as $uri => $settings) {
|
||||
$component = $settings['component'] ?? '';
|
||||
$middlewares = $settings['middlewares'] ?? [];
|
||||
$name = $settings['name'] ?? null;
|
||||
$loginRequired = $settings['loginRequired'] ?? false;
|
||||
$allowedUserTypes = $settings['allowedUserTypes'] ?? [];
|
||||
|
||||
$options = [];
|
||||
|
||||
if (!empty($middlewares)) {
|
||||
$options['middleware'] = $middlewares;
|
||||
}
|
||||
|
||||
if ($name) {
|
||||
$options['as'] = $name;
|
||||
}
|
||||
|
||||
// Add login requirement middleware if needed
|
||||
if ($loginRequired) {
|
||||
$options['middleware'] = array_unique(array_merge($options['middleware'] ?? [], ['auth']));
|
||||
}
|
||||
|
||||
// Add module requirement middleware if specified
|
||||
$moduleKey = $settings['module'] ?? null;
|
||||
if ($moduleKey) {
|
||||
$options['middleware'] = array_unique(array_merge($options['middleware'] ?? [], ["module:{$moduleKey}"]));
|
||||
}
|
||||
|
||||
Route::get($uri, function (...$routeParams) use ($component, $loginRequired, $allowedUserTypes, $moduleKey) {
|
||||
// Check if user is authenticated
|
||||
if (empty(Auth::user())) {
|
||||
// If login is required but not available, redirect to login page
|
||||
if ($loginRequired) {
|
||||
return redirect('/login');
|
||||
}
|
||||
}
|
||||
|
||||
// Check if module is enabled
|
||||
if ($moduleKey && ModuleHelper::isDisabled($moduleKey)) {
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
// Global Page Disabled Check
|
||||
/** @var \App\Models\User $user */
|
||||
$user = Auth::user();
|
||||
$disabledPages = \App\Models\SystemSetting::getValue('disabled_pages', []);
|
||||
if (is_array($disabledPages) && in_array(strtolower((string)$component), array_map('strtolower', $disabledPages))) {
|
||||
// Ultimate accounts can still access to allow fixing settings
|
||||
if (!$user || $user->acct_type !== UserTypes::ULTIMATE) {
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
|
||||
// Check allowed user types if specified
|
||||
if (!empty($allowedUserTypes)) {
|
||||
$currentUserType = $user->acct_type?->value ?? $user->acct_type ?? UserTypes::PUBLIC->value;
|
||||
$isAllowed = self::isUserAllowed($currentUserType, $allowedUserTypes);
|
||||
if (!$isAllowed) {
|
||||
// Redirect to a forbidden page or login
|
||||
return redirect('/login');
|
||||
}
|
||||
}
|
||||
|
||||
// Return Inertia page properly set up with current user data
|
||||
// Pass parameterized URL segments to the Vue component as properties
|
||||
$page = Inertia::render($component, [
|
||||
'user' => Auth::user(),
|
||||
'routeParams' => empty($routeParams) ? null : $routeParams
|
||||
]);
|
||||
|
||||
return view('layouts/application-layout', compact('page'));
|
||||
}, $options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a generic SPA request by converting the path into a component name.
|
||||
* This acts as a catch-all universal router.
|
||||
*/
|
||||
public static function handleSpa(string $path = '/')
|
||||
{
|
||||
$path = trim($path, '/');
|
||||
|
||||
// Get user type for access control
|
||||
/** @var \App\Models\User $user */
|
||||
$user = Auth::user();
|
||||
$currentUserType = null;
|
||||
|
||||
if ($user) {
|
||||
$currentUserType = $user->acct_type?->value ?? $user->acct_type ?? UserTypes::PUBLIC->value;
|
||||
}
|
||||
|
||||
// Strip hashkey/payload suffix from path (e.g., "edituser--h:HASHKEY" -> "edituser")
|
||||
// Use RouteArgumentParser to properly extract the base component name
|
||||
$component = $path;
|
||||
|
||||
try {
|
||||
$parser = new \App\Support\RouteArgumentParser();
|
||||
$parsedData = $parser->parseArgument($path);
|
||||
|
||||
// If we have a hash or payload format, use the slug as the component
|
||||
if (isset($parsedData['slug']) && ($parsedData['type'] === 'hash' || $parsedData['type'] === 'payload')) {
|
||||
$component = $parsedData['slug'];
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
// If parsing fails or no hash/payload format, use original path
|
||||
}
|
||||
|
||||
// fallback to Home if empty
|
||||
$component = empty($component) ? 'Home' : $component;
|
||||
|
||||
// Convert lowercase component names to camelCase for Vue component matching
|
||||
// e.g., "edituser" -> "EditUser"
|
||||
$vueComponent = self::toCamelCase($component);
|
||||
|
||||
// Find base component from routes map using the route key
|
||||
$componentLower = strtolower($component);
|
||||
$routeKey = '/' . $componentLower;
|
||||
|
||||
$loginRequired = true; // Default: require login
|
||||
$pathWithSlash = '/' . ltrim($path, '/');
|
||||
$pathLower = strtolower($pathWithSlash);
|
||||
|
||||
$routeSettings = self::$routes[$pathWithSlash] ?? self::$routes[$pathLower] ?? null;
|
||||
$allowedUserTypes = [];
|
||||
$moduleKey = null;
|
||||
$foundInMap = false;
|
||||
|
||||
if ($routeSettings) {
|
||||
$settings = $routeSettings;
|
||||
$vueComponent = $settings['component'] ?? $vueComponent;
|
||||
$loginRequired = $settings['loginRequired'] ?? true;
|
||||
$allowedUserTypes = $settings['allowedUserTypes'] ?? [];
|
||||
$moduleKey = $settings['module'] ?? null;
|
||||
$foundInMap = true;
|
||||
} else {
|
||||
// Try hyphen-insensitive match (e.g. "viewstoremarket" vs "view-store-market")
|
||||
$cleanSlug = str_replace(['-', '_'], '', $componentLower);
|
||||
foreach (self::$routes as $uri => $settings) {
|
||||
$cleanUri = str_replace(['-', '_'], '', strtolower(trim($uri, '/')));
|
||||
if ($cleanUri === $cleanSlug) {
|
||||
$vueComponent = $settings['component'] ?? $vueComponent;
|
||||
$loginRequired = $settings['loginRequired'] ?? true;
|
||||
$allowedUserTypes = $settings['allowedUserTypes'] ?? [];
|
||||
$moduleKey = $settings['module'] ?? null;
|
||||
$foundInMap = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$foundInMap) {
|
||||
// Fallback: search by Vue component name (case-insensitive) in case they passed the component direct name
|
||||
foreach (self::$routes as $uri => $settings) {
|
||||
$mappedComponent = $settings['component'] ?? '';
|
||||
if (strcasecmp($mappedComponent, $vueComponent) === 0) {
|
||||
$vueComponent = $mappedComponent; // Use the correctly cased name from map
|
||||
$loginRequired = $settings['loginRequired'] ?? true;
|
||||
$allowedUserTypes = $settings['allowedUserTypes'] ?? [];
|
||||
$moduleKey = $settings['module'] ?? null;
|
||||
$foundInMap = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce login requirement
|
||||
if ($loginRequired && empty($user)) {
|
||||
return redirect('/login');
|
||||
}
|
||||
|
||||
// Enforce module requirement
|
||||
if ($moduleKey && ModuleHelper::isDisabled($moduleKey)) {
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
// For store-level users, check the store-specific module toggle
|
||||
$storeModuleKey = $routeSettings['store_module'] ?? null;
|
||||
if ($storeModuleKey && in_array($currentUserType, ['store owner', 'store manager'])) {
|
||||
if (ModuleHelper::isDisabled($storeModuleKey)) {
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
|
||||
// Global Page Disabled Check
|
||||
$disabledPages = \App\Models\SystemSetting::getValue('disabled_pages', []);
|
||||
if (is_array($disabledPages) && in_array(strtolower((string)$vueComponent), array_map('strtolower', $disabledPages))) {
|
||||
// Ultimate accounts can still access to allow fixing settings
|
||||
if (!$user || $user->acct_type !== UserTypes::ULTIMATE) {
|
||||
return redirect('/');
|
||||
}
|
||||
}
|
||||
|
||||
// Check allowed user types if specified
|
||||
if (!empty($allowedUserTypes) && $currentUserType !== null) {
|
||||
$isAllowed = self::isUserAllowed($currentUserType, $allowedUserTypes);
|
||||
if (!$isAllowed) {
|
||||
// Redirect to a forbidden page or login
|
||||
return redirect('/login');
|
||||
}
|
||||
}
|
||||
|
||||
// Pass standard Inertia props with hashkey if present
|
||||
$props = [
|
||||
'user' => Auth::user(),
|
||||
];
|
||||
|
||||
// Add hashkey to props if it was in the URL for special pages
|
||||
if (isset($parsedData['type']) && $parsedData['type'] === 'hash') {
|
||||
// Provide the hash value under multiple common prop names for better compatibility
|
||||
$props['hashkey'] = $parsedData['value'];
|
||||
$props['target'] = $parsedData['value'];
|
||||
$props['id'] = $parsedData['value'];
|
||||
} elseif (isset($parsedData['type']) && $parsedData['type'] === 'payload') {
|
||||
$props['payload'] = $parsedData['value'];
|
||||
}
|
||||
|
||||
$page = Inertia::render($vueComponent, $props);
|
||||
|
||||
return view('layouts/application-layout', compact('page'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert snake_case or lowercase to camelCase for Vue component matching.
|
||||
*/
|
||||
private static function toCamelCase(string $name): string
|
||||
{
|
||||
// Replace slashes with dots first to handle directory structures
|
||||
$name = str_replace('/', '.', $name);
|
||||
|
||||
// Split by dot (hierarchy)
|
||||
$parts = explode('.', $name);
|
||||
$pascalParts = array_map(function($part) {
|
||||
// Split by hyphen and capitalize each part for PascalCase
|
||||
$subParts = explode('-', $part);
|
||||
return implode('', array_map('ucfirst', $subParts));
|
||||
}, $parts);
|
||||
|
||||
// Return with dots if original had them, otherwise just one string
|
||||
return implode('.', $pascalParts);
|
||||
}
|
||||
}
|
||||
78
app/Http/Controllers/UserCreateController.php
Normal file
78
app/Http/Controllers/UserCreateController.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\UserTypes;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use Hypervel\Http\Request;
|
||||
use App\Models\User;
|
||||
|
||||
|
||||
use App\Enums\UserActions;
|
||||
use App\Traits\Roles;
|
||||
use Hypervel\Support\Facades\Hash;
|
||||
use Hypervel\Support\Facades\Validator;
|
||||
|
||||
|
||||
class UserCreateController
|
||||
{
|
||||
public function createUser(UserTypes $acct_type, Request $request)
|
||||
{
|
||||
// Step 1: Check if the current authenticated user has the permission to create a user
|
||||
$userType = auth()->user()->acct_type; // Assuming you're using the `acct_type` field for the current user's type
|
||||
|
||||
|
||||
|
||||
if (!UserPermissions::isActionPermitted($acct_type, UserActions::CreateUser)) {
|
||||
return response()->json(['error' => 'Permission denied'], 403);
|
||||
}
|
||||
|
||||
// Step 2: Validate incoming request data
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|string|max:255',
|
||||
|
||||
|
||||
'email' => 'required|email|unique:users,email',
|
||||
'mobile_number' => 'required|string|max:15',
|
||||
'password' => 'required|string|min:8',
|
||||
'username' => 'nullable|string|unique:users,username',
|
||||
// Add any other validation rules needed
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json(['errors' => $validator->errors()], 422);
|
||||
}
|
||||
|
||||
if ($acct_type instanceof UserTypes) {
|
||||
$acct_type = $acct_type->value;
|
||||
}
|
||||
|
||||
if (!is_string($acct_type) || !$acct_type) {
|
||||
|
||||
}
|
||||
|
||||
// Step 3: Create the new user
|
||||
$user = User::create([
|
||||
'name' => $request->input('name'),
|
||||
'email' => $request->input('email'),
|
||||
'mobile_number' => $request->input('mobile_number'),
|
||||
'password' => Hash::make($request->input('password')),
|
||||
'acct_type' => $acct_type,
|
||||
'username' => $request->input('username'),
|
||||
'created_by' => auth()->user()->id, // Currently authenticated user
|
||||
// Add any other fields as needed
|
||||
]);
|
||||
|
||||
// Step 4: Handle user-specific logic based on their `acct_type`
|
||||
$this->handleUserTypeSpecificLogic($acct_type, $user);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'User created successfully',
|
||||
'user' => $user
|
||||
], 201);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\UserManagement;
|
||||
|
||||
use App\Enums\UserActions;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use Hypervel\Http\Request;
|
||||
use App\Enums\UserTypes;
|
||||
use Hypervel\Support\Facades\Hash;
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use App\Models\User;
|
||||
|
||||
class CreateUserControllerUltimate
|
||||
{
|
||||
|
||||
public function listAllUserTypesforSelectHTML()
|
||||
{
|
||||
$currentUser = \Hypervel\Support\Facades\Auth::user();
|
||||
if (!$currentUser) {
|
||||
return Response::json([], 200);
|
||||
}
|
||||
|
||||
if (!UserPermissions::isActionPermitted($currentUser->acct_type, UserActions::ViewAllUserTypes)) {
|
||||
return Response::json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$currentUserType = $currentUser->acct_type;
|
||||
$allowedTypes = \App\Http\Controllers\Helpers\Permissions\UserTypeService::getAllowedUserTypes($currentUserType);
|
||||
|
||||
$formatted = [];
|
||||
foreach ($allowedTypes as $case) {
|
||||
$label = str_replace('_', ' ', ucwords(strtolower($case->name)));
|
||||
$formatted[] = [$case->value, $label];
|
||||
}
|
||||
|
||||
return Response::json($formatted);
|
||||
}
|
||||
|
||||
public static function listAllUsersforParentSelectHTML(Request $request, $dataResult = false)
|
||||
{
|
||||
$currentUser = \Hypervel\Support\Facades\Auth::user();
|
||||
|
||||
if (!$currentUser) {
|
||||
return Response::json([], 200);
|
||||
}
|
||||
|
||||
// Ultimate accounts can see all users
|
||||
if ($currentUser->acct_type === UserTypes::ULTIMATE) {
|
||||
$allowedIds = null;
|
||||
} else {
|
||||
// Only show current user and their descendants (direct or indirect children)
|
||||
try {
|
||||
$descendants = $currentUser->getAllDescendants();
|
||||
$allowedIds = $descendants->pluck('id')->toArray();
|
||||
$allowedIds[] = $currentUser->id;
|
||||
} catch (\Throwable $th) {
|
||||
return Response::json([], 200);
|
||||
}
|
||||
}
|
||||
|
||||
$excludeUser = $request->input('exclude_user', null);
|
||||
$typeFilter = $request->input('type', null);
|
||||
|
||||
$usersQuery = User::select(['id', 'username', 'name', 'fullname', 'mobile_number', 'hashkey', 'acct_type']);
|
||||
|
||||
if ($allowedIds !== null) {
|
||||
$usersQuery = $usersQuery->whereIn('id', $allowedIds);
|
||||
}
|
||||
|
||||
// Exclude the specified user if provided
|
||||
if ($excludeUser) {
|
||||
$usersQuery = $usersQuery->where('hashkey', '!=', $excludeUser);
|
||||
}
|
||||
|
||||
if ($typeFilter) {
|
||||
$types = is_array($typeFilter) ? $typeFilter : [$typeFilter];
|
||||
$usersQuery = $usersQuery->whereIn('acct_type', $types);
|
||||
}
|
||||
|
||||
$users = $usersQuery->get();
|
||||
|
||||
if (!$dataResult) {
|
||||
return Response::json($users);
|
||||
} else {
|
||||
return $users;
|
||||
}
|
||||
}
|
||||
|
||||
public function CreateUser(Request $request)
|
||||
{
|
||||
$usertypeString = $request->input('type');
|
||||
|
||||
if (!is_string($usertypeString) || empty($usertypeString)) {
|
||||
return Response::json(['error' => 'User type is required'], 400);
|
||||
}
|
||||
|
||||
$usertypeEnum = UserTypes::tryFrom($usertypeString);
|
||||
if (!$usertypeEnum) {
|
||||
return Response::json(['error' => 'Invalid User Type'], 400);
|
||||
}
|
||||
|
||||
// Map UserTypes to specialized CreateUser UserActions
|
||||
$action = match ($usertypeEnum) {
|
||||
UserTypes::ULTIMATE => UserActions::CreateUserUltimate,
|
||||
UserTypes::SUPER_OPERATOR => UserActions::CreateUserSuperOperator,
|
||||
UserTypes::OPERATOR => UserActions::CreateUserOperator,
|
||||
UserTypes::COORDINATOR => UserActions::CreateUserCoordinator,
|
||||
UserTypes::SUPPLIER_OVERSEER => UserActions::CreateUserSupplierOverseer,
|
||||
UserTypes::WHOLESALE_BUYER => UserActions::CreateUserWholesaleBuyer,
|
||||
UserTypes::SUPPLIER => UserActions::CreateUserSupplier,
|
||||
UserTypes::STORE_OWNER => UserActions::CreateUserStoreOwner,
|
||||
UserTypes::STORE_MANAGER => UserActions::CreateUserStoreManager,
|
||||
UserTypes::USER => UserActions::CreateUserUser,
|
||||
UserTypes::RIDER => UserActions::CreateUserRider,
|
||||
UserTypes::POS_TERMINAL => UserActions::CreateUserPOSTerminal,
|
||||
UserTypes::AUDIT => UserActions::CreateUserAudit,
|
||||
default => UserActions::CreateUser,
|
||||
};
|
||||
|
||||
$currentUser = \Hypervel\Support\Facades\Auth::user();
|
||||
$targetParentHash = $request->input('parent');
|
||||
|
||||
if (!$currentUser) {
|
||||
return Response::json(['error' => 'Unauthorized'], 401);
|
||||
}
|
||||
|
||||
$currentUserType = $currentUser->acct_type;
|
||||
if (!($currentUserType instanceof UserTypes)) {
|
||||
$currentUserType = UserTypes::tryFrom($currentUserType) ?? UserTypes::PUBLIC;
|
||||
}
|
||||
|
||||
if ($currentUserType !== UserTypes::ULTIMATE) {
|
||||
// Check the new user's type is in the allowed list for this creator
|
||||
$allowedTypes = \App\Http\Controllers\Helpers\Permissions\UserTypeService::getAllowedUserTypes($currentUserType);
|
||||
if (!in_array($usertypeEnum, $allowedTypes)) {
|
||||
return Response::json(['error' => 'You are not allowed to create this user type.'], 401);
|
||||
}
|
||||
|
||||
// Check that the chosen parent is the current user or a descendant
|
||||
if ($targetParentHash) {
|
||||
$isParentSelfOrDescendant = ($currentUser->hashkey === $targetParentHash)
|
||||
|| UserPermissions::isDescendantOfCurrentUser($targetParentHash);
|
||||
if (!$isParentSelfOrDescendant) {
|
||||
return Response::json(['error' => 'Parent user is not in your hierarchy.'], 401);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
$mobileRules = ['required', 'string', 'max:20', 'unique:users,mobile_number'];
|
||||
if (!UserPermissions::isActionPermitted($currentUser->acct_type, UserActions::BypassMobileNumberFormat)) {
|
||||
$mobileRules[] = 'regex:/^(09|\+639)\d{9}$/';
|
||||
}
|
||||
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'username' => 'required|string|max:255|unique:users,username',
|
||||
'name' => 'required|string|max:255',
|
||||
'fullname' => 'nullable|string|max:255',
|
||||
'mobile_number' => $mobileRules,
|
||||
'password' => 'required|string|min:6',
|
||||
'nickname' => 'nullable|string|max:255',
|
||||
'parent' => 'required|string',
|
||||
'type' => 'required|string',
|
||||
]);
|
||||
} catch (\Hypervel\Validation\ValidationException $e) {
|
||||
return Response::json(['errors' => $e->errors()], 422);
|
||||
}
|
||||
|
||||
$parentUser = User::where('hashkey', $validated['parent'])->first();
|
||||
|
||||
if (!$parentUser) {
|
||||
return Response::json(['error' => 'Parent user not found'], 404);
|
||||
}
|
||||
|
||||
$parent = $parentUser->id;
|
||||
|
||||
|
||||
$user = new User();
|
||||
$user->username = $validated['username'];
|
||||
$user->name = $validated['name'];
|
||||
$user->fullname = $validated['fullname'] ?? null;
|
||||
$user->mobile_number = $validated['mobile_number'];
|
||||
$user->password = Hash::make($validated['password']);
|
||||
$user->nickname = $validated['nickname'] ?? null;
|
||||
$user->parentuid = $parent;
|
||||
$user->acct_type = $validated['type'];
|
||||
$user->active = true;
|
||||
$user->save();
|
||||
|
||||
return Response::json(['success' => true, 'hashkey' => $user->hashkey, 'message' => 'User created successfully'], 201);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function checkIfUserMobileNumberExists(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'mobile_number' => 'required|string',
|
||||
]);
|
||||
$mobileNumber = $request->input('mobile_number');
|
||||
$userExists = User::where('mobile_number', $mobileNumber)->exists();
|
||||
return Response::json(['exists' => $userExists]);
|
||||
}
|
||||
|
||||
public function checkIfUsernameExists(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'username' => 'required|string',
|
||||
]);
|
||||
$username = $request->input('username');
|
||||
$userExists = User::where('username', $username)->exists();
|
||||
return Response::json(['exists' => $userExists]);
|
||||
}
|
||||
|
||||
public function publicRegisterUser(Request $request)
|
||||
{
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'mobile_number' => 'required|string|max:20|unique:users,mobile_number|regex:/^(09|\+639)\d{9}$/',
|
||||
'password' => 'required|string|min:6',
|
||||
'nickname' => 'nullable|string|max:255',
|
||||
]);
|
||||
} catch (\Hypervel\Validation\ValidationException $e) {
|
||||
return Response::json(['success' => false, 'errors' => $e->errors()], 422);
|
||||
}
|
||||
|
||||
$parent = User::where('acct_type', UserTypes::ULTIMATE->value)->orderBy('id')->first();
|
||||
|
||||
if (!$parent) {
|
||||
$parent = User::where('acct_type', UserTypes::COORDINATOR->value)->orderBy('id')->first();
|
||||
}
|
||||
|
||||
if (!$parent) {
|
||||
$parent = User::orderBy('id')->first();
|
||||
}
|
||||
|
||||
if (!$parent) {
|
||||
return Response::json(['success' => false, 'message' => 'No valid parent user found'], 500);
|
||||
}
|
||||
|
||||
$user = new User();
|
||||
$user->name = $validated['name'];
|
||||
$user->mobile_number = $validated['mobile_number'];
|
||||
$user->password = Hash::make($validated['password']);
|
||||
$user->nickname = $validated['nickname'] ?? null;
|
||||
$user->parentuid = $parent->id;
|
||||
$user->acct_type = 'user';
|
||||
$user->active = true;
|
||||
$user->save();
|
||||
|
||||
return Response::json(['success' => true, 'hashkey' => $user->hashkey, 'message' => 'Account created successfully. Please log in.'], 201);
|
||||
}
|
||||
|
||||
public function publicCheckMobileNumber(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'mobile_number' => 'required|string',
|
||||
]);
|
||||
|
||||
return Response::json(['exists' => User::where('mobile_number', $request->input('mobile_number'))->exists()]);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\UserManagement;
|
||||
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
|
||||
use App\Enums\UserActions;
|
||||
use App\Models\User;
|
||||
use App\Models\Market\Organization;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Support\Str;
|
||||
|
||||
class UserAdditionalDetailsController
|
||||
{
|
||||
public function getDetails(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'settings' => $user->settings,
|
||||
'details' => $user->details,
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateCooperatives(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (!$user) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$cooperativeHash = $request->input('cooperative_hash');
|
||||
$action = $request->input('action', 'add'); // 'add' or 'remove'
|
||||
|
||||
if (!$cooperativeHash) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
$settings = $user->settings ?? [];
|
||||
$cooperatives = $settings['cooperatives'] ?? [];
|
||||
|
||||
if ($action === 'add') {
|
||||
if (!in_array($cooperativeHash, $cooperatives)) {
|
||||
$cooperatives[] = $cooperativeHash;
|
||||
}
|
||||
} else {
|
||||
$cooperatives = array_values(array_filter($cooperatives, fn($h) => $h !== $cooperativeHash));
|
||||
}
|
||||
|
||||
$settings['cooperatives'] = $cooperatives;
|
||||
$user->settings = $settings;
|
||||
|
||||
if ($user->save()) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Cooperatives updated successfully',
|
||||
'data' => $cooperatives
|
||||
]);
|
||||
}
|
||||
|
||||
return ResponseHelper::returnError('Failed to update cooperatives');
|
||||
}
|
||||
|
||||
public function getUserCooperatives(Request $request)
|
||||
{
|
||||
$userHash = $request->input('user_hash');
|
||||
|
||||
if ($userHash) {
|
||||
$targetUser = User::where('hashkey', $userHash)->first();
|
||||
if (!$targetUser) {
|
||||
return ResponseHelper::returnError('User not found', 404);
|
||||
}
|
||||
|
||||
// Authorization check
|
||||
if (!UserPermissions::isActionPermitted($targetUser->acct_type, UserActions::ViewUserInfo)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$user = $targetUser;
|
||||
} else {
|
||||
$user = Auth::user();
|
||||
}
|
||||
|
||||
if (!$user) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$cooperativeHashes = $user->settings['cooperatives'] ?? [];
|
||||
if (empty($cooperativeHashes)) {
|
||||
return response()->json(['success' => true, 'data' => []]);
|
||||
}
|
||||
|
||||
$cooperatives = Organization::whereIn('hashkey', $cooperativeHashes)->get();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $cooperatives
|
||||
]);
|
||||
}
|
||||
|
||||
public function searchUsersByCooperative(Request $request)
|
||||
{
|
||||
if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewUserInfo)) {
|
||||
return ResponseHelper::returnUnauthorized();
|
||||
}
|
||||
|
||||
$cooperativeHash = $request->input('cooperative_hash');
|
||||
if (!$cooperativeHash) {
|
||||
return ResponseHelper::returnIncorrectDetails();
|
||||
}
|
||||
|
||||
// Search in the JSON field 'settings' for cooperatives array containing the hash
|
||||
$users = User::where('settings->cooperatives', 'like', '%' . $cooperativeHash . '%')->get();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $users
|
||||
]);
|
||||
}
|
||||
}
|
||||
14
app/Http/Controllers/UserPages/UltimateUserController.php
Normal file
14
app/Http/Controllers/UserPages/UltimateUserController.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\UserPages;
|
||||
|
||||
use Hypervel\Http\Request;
|
||||
|
||||
class UltimateUserController
|
||||
{
|
||||
public function Home(){
|
||||
|
||||
}
|
||||
}
|
||||
438
app/Http/Controllers/viewHelperController.php
Normal file
438
app/Http/Controllers/viewHelperController.php
Normal file
@@ -0,0 +1,438 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\UserTypes;
|
||||
use Hyperf\Codec\Json;
|
||||
use Hyperf\Config\Config;
|
||||
use Hypervel\Http\Request;
|
||||
use Hypervel\Support\Facades\File;
|
||||
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
|
||||
use Hypervel\Support\Facades\Response;
|
||||
use App\Http\Controllers\Pages\UserModifyAdminPageController;
|
||||
use Hypervel\Support\Facades\Storage;
|
||||
|
||||
use App\Models\SystemSetting;
|
||||
use Hyperf\Support\Facades\Logger;
|
||||
|
||||
|
||||
class viewHelperController
|
||||
{
|
||||
|
||||
public static function getDefaultDataVariables(): array
|
||||
{
|
||||
return [
|
||||
'currentUser' => Auth::user(),
|
||||
'userModifyAdmin' => new UserModifyAdminPageController(),
|
||||
'echo' => [
|
||||
'InitializeLoadedFlag' => function () {
|
||||
echo '<script> if (typeof DataLoadedFlag === "undefined"){ let DataLoadedFlag = false; } </script>';
|
||||
},
|
||||
'SetLoadedFlagTrue' => function () {
|
||||
echo '<script>if (typeof DataLoadedFlag === "undefined"){ let DataLoadedFlag;} DataLoadedFlag = true; </script>';
|
||||
},
|
||||
'SetLoadedFlagFalse' => function () {
|
||||
echo '<script> if (typeof DataLoadedFlag === "undefined"){ let DataLoadedFlag;} DataLoadedFlag = false; </script>';
|
||||
},
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public static function tryjsondecode($string, $arrayoutput = true)
|
||||
{
|
||||
if (is_array($string)) {
|
||||
return $string;
|
||||
}
|
||||
if (!$string) {
|
||||
return $string;
|
||||
}
|
||||
$json = json_decode($string, $arrayoutput);
|
||||
if ($json === null) {
|
||||
return $string;
|
||||
} else {
|
||||
return $json;
|
||||
}
|
||||
}
|
||||
|
||||
public static function tryjsonencode($array)
|
||||
{
|
||||
if (!$array) {
|
||||
return json_encode([]);
|
||||
}
|
||||
if (is_array($array)) {
|
||||
$result = json_encode($array);
|
||||
} else {
|
||||
$result = json_encode([$array]);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function getAllViews($path = null, $base = 'resources/views')
|
||||
{
|
||||
$path = $path ?? base_path($base);
|
||||
$views = [];
|
||||
|
||||
foreach (File::allFiles($path) as $file) {
|
||||
$relativePath = $file->getRelativePathname();
|
||||
|
||||
if (!str_ends_with($relativePath, '.blade.php')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$view = str_replace(['/', '\\'], '.', $relativePath);
|
||||
$view = str_replace('.blade.php', '', $view);
|
||||
$views[] = $view;
|
||||
}
|
||||
|
||||
return $views;
|
||||
}
|
||||
|
||||
public static function decodeData(string|int|bool|null $data = 0)
|
||||
{
|
||||
if (is_string($data)) {
|
||||
$decodedData = urldecode($data);
|
||||
if (str_contains($decodedData, '{')) {
|
||||
$val = json_decode($data, true);
|
||||
} elseif (str_contains($decodedData, ',')) {
|
||||
$decodedData = explode(',', $decodedData);
|
||||
$decodedData = self::tryjsonencode($decodedData);
|
||||
} else {
|
||||
$current_target = $data;
|
||||
// $val = ['current_target' => $current_target];
|
||||
$decodedData = "'" . $decodedData . "'";
|
||||
}
|
||||
} else {
|
||||
$decodedData = 0;
|
||||
}
|
||||
|
||||
return $decodedData;
|
||||
}
|
||||
|
||||
private static function generatePageHtml(string|array $viewPath, $data, $withTemplate)
|
||||
{
|
||||
$viewData = [];
|
||||
$viewData['FragmentOnly'] = !$withTemplate;
|
||||
$viewData['current_target'] = $data;
|
||||
$viewData['current_data'] = false;
|
||||
$viewData['viewPath'] = $viewPath;
|
||||
// $viewMap = config('viewmap.php');
|
||||
|
||||
if (Auth::check()) {
|
||||
$viewData['currentUser'] = Auth::user();
|
||||
$viewData['current_user'] = $viewData['currentUser'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (is_array($viewPath)) {
|
||||
$closure = $viewPath['data'] ?? null;
|
||||
$link = $viewPath['link'] ?? null;
|
||||
|
||||
$viewData['viewisArray'] = true;
|
||||
|
||||
if ($closure && $closure instanceof \Closure && is_string($data)) {
|
||||
// $viewData['viewisClosure'] = true;
|
||||
$current_data = $closure($data);
|
||||
$viewData['viewisClosure'] = true;
|
||||
} elseif (is_array($data) || is_object($data)) {
|
||||
// $viewData['viewisObject'] = true;
|
||||
$current_data = $data;
|
||||
} else {
|
||||
// $viewData['viewisfalse'] = true;
|
||||
$current_data = false;
|
||||
}
|
||||
|
||||
$viewPath = $link;
|
||||
|
||||
|
||||
|
||||
// file_put_contents('viewvars.txt', print_r(self::getDefaultDataVariables() . $viewData, 1));
|
||||
|
||||
$viewData = array_merge(self::getDefaultDataVariables(), $viewData);
|
||||
|
||||
$viewData['current_data'] = $current_data;
|
||||
|
||||
$pageHtml = view($viewPath, $viewData)->render();
|
||||
return $pageHtml;
|
||||
}
|
||||
|
||||
|
||||
return view($viewPath, $viewData)->render();
|
||||
}
|
||||
|
||||
private static function generatePageHtmlNew(string|array $viewPath, $data, bool $withTemplate)
|
||||
{
|
||||
$viewData = [
|
||||
'FragmentOnly' => !$withTemplate,
|
||||
'current_target' => $data,
|
||||
'current_data' => false,
|
||||
];
|
||||
|
||||
// Handle associative array input (with closure support)
|
||||
if (is_array($viewPath)) {
|
||||
$closure = $viewPath['data'] ?? null;
|
||||
$link = $viewPath['link'] ?? null;
|
||||
|
||||
// Validate
|
||||
if (!$link) {
|
||||
trigger_error("Missing 'link' key in viewPath array.", E_USER_ERROR);
|
||||
}
|
||||
|
||||
// Execute closure if provided
|
||||
if ($closure instanceof \Closure) {
|
||||
$current_data = $closure($data);
|
||||
} elseif (is_array($data) || is_object($data)) {
|
||||
$current_data = $data;
|
||||
} else {
|
||||
$current_data = false;
|
||||
}
|
||||
|
||||
$viewPath = $link;
|
||||
$viewData['current_data'] = $current_data;
|
||||
}
|
||||
|
||||
// Add default + user variables
|
||||
$viewData = array_merge(self::getDefaultDataVariables(), $viewData);
|
||||
|
||||
if (Auth::check()) {
|
||||
$viewData['current_user'] = Auth::user();
|
||||
}
|
||||
|
||||
// Render
|
||||
try {
|
||||
return view($viewPath, $viewData)->render();
|
||||
} catch (\Throwable $e) {
|
||||
trigger_error("Error rendering view '$viewPath': " . $e->getMessage(), E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public function servePageFragmentUnified($page, $data = 0, $withTemplate = false)
|
||||
{
|
||||
$pagename = $page;
|
||||
$val = $data ?? '';
|
||||
$loginstatus = Auth::check();
|
||||
$viewMap = config('viewmap');
|
||||
|
||||
// Build list of public pages
|
||||
$publicPages = [];
|
||||
foreach ($viewMap as $route => $views) {
|
||||
if (!empty($views['public'])) {
|
||||
$publicPages[] = ltrim($route, '/');
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect unauthenticated users trying to access private pages
|
||||
if (!$loginstatus && !in_array($pagename, $publicPages)) {
|
||||
return response('<script>window.location.href = "/";</script>', 403, ['Content-Type' => 'text/html']);
|
||||
}
|
||||
|
||||
if (!$pagename) {
|
||||
return response('Invalid page.', 400);
|
||||
}
|
||||
|
||||
// Backend Interception for Disabled Pages
|
||||
if ($this->isPageDisabled($pagename)) {
|
||||
if (!$this->canAccessDisabledPage()) {
|
||||
// Non-Ultimate users redirect to home (consistent with Vue check)
|
||||
return response('<script>window.location.href = "/";</script>', 403, ['Content-Type' => 'text/html']);
|
||||
}
|
||||
}
|
||||
|
||||
// Determine user type
|
||||
$userType = $loginstatus ? Auth::user()->acct_type->value : 'public';
|
||||
|
||||
try {
|
||||
$viewPath = $viewMap[$pagename][$userType] ?? null;
|
||||
} catch (\Throwable $th) {
|
||||
$viewPath = null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
$viewPathDefault = $viewMap[$pagename]['default'] ?? $viewMap[$pagename]['public'] ?? null;
|
||||
|
||||
if (!$viewPath && !$viewPathDefault) {
|
||||
if (Auth::check() && Auth::user()->acct_type->value === UserTypes::ULTIMATE->value) {
|
||||
return response("View for page '{$pagename}' and user type '{$userType}' not found.", 404);
|
||||
} else {
|
||||
return abort(404, 'Page not found.');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
$viewPath = $viewPath ?: $viewPathDefault;
|
||||
|
||||
|
||||
// if (is_array($viewPath)) {
|
||||
// // if (!$data) {
|
||||
// // $val = $viewPath['data'] ?? 0;
|
||||
// // }
|
||||
// $viewPath = $viewPath['link'];
|
||||
// }
|
||||
|
||||
$dontInitializeScript = $userType === 'public'
|
||||
? '<script>DontInitialize = 1;</script>'
|
||||
: '';
|
||||
|
||||
$decodedData = self::decodeData($data);
|
||||
|
||||
|
||||
|
||||
$pageHtml = self::generatePageHtml($viewPath, $data, $withTemplate);
|
||||
|
||||
|
||||
|
||||
if ($withTemplate) {
|
||||
|
||||
$htmlShell = view('layouts.default')->render();
|
||||
$pageHtmlEncoded = base64_encode($pageHtml);
|
||||
|
||||
$gotoScript = <<<HTML
|
||||
<script>
|
||||
|
||||
$(document).ready(function () {
|
||||
fragmentonly = false;
|
||||
gotoPage("{$pagename}", {$decodedData}, 0, 1, "{$pageHtmlEncoded}");
|
||||
});
|
||||
</script>
|
||||
HTML;
|
||||
|
||||
$output = $htmlShell . $dontInitializeScript . $gotoScript;
|
||||
} else {
|
||||
|
||||
$pageHtml .= '<script>window.fragmentonly=true;</script>';
|
||||
$output = $pageHtml;
|
||||
}
|
||||
|
||||
return response($output, 200, ['Content-Type' => 'text/html']);
|
||||
}
|
||||
|
||||
|
||||
public function servePageFragmentWithTemplate($page, $data = 0)
|
||||
{
|
||||
return $this->servePageFragmentUnified($page, $data, true);
|
||||
}
|
||||
|
||||
public function servePageFragment($page, $data = 0)
|
||||
{
|
||||
return $this->servePageFragmentUnified($page, $data, false);
|
||||
}
|
||||
|
||||
|
||||
// public function servePageFragment($page, $data=0)
|
||||
// {
|
||||
// $pagename = $page;
|
||||
// $val = $data ?? '';
|
||||
// $loginstatus = Auth::check();
|
||||
// $viewMap = config('viewmap');
|
||||
// $publicPages = [];
|
||||
|
||||
// foreach ($viewMap as $route => $views) {
|
||||
// if (!empty($views['public'])) {
|
||||
// $publicPages[] = ltrim($route, '/');
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// if (!$loginstatus && !in_array($pagename, $publicPages)) {
|
||||
// return response('<script>window.location.href = "/";</script>', 403);
|
||||
// }
|
||||
|
||||
// if (!$pagename) {
|
||||
// return response('Invalid page.', 400);
|
||||
// }
|
||||
|
||||
|
||||
// $userType = $loginstatus ? (Auth::user()->acct_type ?? 'default') : 'public';
|
||||
|
||||
|
||||
// $viewPath = $viewMap[$pagename][$userType] ?? null;
|
||||
|
||||
// if (!$viewPath) {
|
||||
// return response("View for page '{$pagename}' and user type '{$userType}' not found.", 404);
|
||||
// }
|
||||
|
||||
// // Optional: flag to prevent initialization on public pages
|
||||
// $dontInitializeScript = '';
|
||||
// if ($userType === 'public') {
|
||||
// $dontInitializeScript = '<script>DontInitialize = 1;</script>';
|
||||
// }
|
||||
|
||||
// // Process the `$data` parameter
|
||||
// if (is_string($val)){
|
||||
// $decodedData = urldecode($val);
|
||||
// if (str_contains($decodedData, '{')) {
|
||||
// // Assume it's already JSON
|
||||
// } elseif (str_contains($decodedData, ',')) {
|
||||
// $decodedData = explode(',', $decodedData);
|
||||
// $decodedData = tryjsonencode($decodedData);
|
||||
// } else {
|
||||
// $decodedData = "'" . $decodedData . "'";
|
||||
// }}else{
|
||||
// $decodedData=0;
|
||||
// }
|
||||
|
||||
// // Load HTML shell layout (starter page)
|
||||
// $htmlShell = view('layouts.default')->render();
|
||||
|
||||
// // Load the page fragment's view content
|
||||
// $pageHtml = view($viewPath)->render();
|
||||
// $pageHtmlEncoded = base64_encode($pageHtml);
|
||||
|
||||
// // Inject script to trigger JS-side page rendering
|
||||
// $gotoScript = <<<HTML
|
||||
// <script>
|
||||
// $(document).ready(function () {
|
||||
// gotoPage("{$pagename}", {$decodedData}, 0, 0, `{$pageHtmlEncoded}`);
|
||||
// });
|
||||
// </script>
|
||||
// HTML;
|
||||
|
||||
// // Return the full response
|
||||
// return response($htmlShell . $dontInitializeScript . $gotoScript, 200, ['Content-Type' => 'text/html']);
|
||||
|
||||
|
||||
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Check if a page is disabled via system setting.
|
||||
*/
|
||||
private function isPageDisabled(string $pageName): bool
|
||||
{
|
||||
$disabledPages = SystemSetting::getValue('disabled_pages', []);
|
||||
|
||||
if (empty($disabledPages) || !is_array($disabledPages)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Case-insensitive match
|
||||
return in_array(strtolower($pageName), array_map('strtolower', $disabledPages));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if current user can access disabled pages (Ultimate only).
|
||||
*/
|
||||
private function canAccessDisabledPage(): bool
|
||||
{
|
||||
if (!Auth::user()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user = Auth::user();
|
||||
return isset($user->acct_type) && $user->acct_type->value === UserTypes::ULTIMATE->value;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
78
app/Http/Kernel.php
Normal file
78
app/Http/Kernel.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
use Hypervel\Foundation\Http\Kernel as HttpKernel;
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
{
|
||||
/**
|
||||
* The application's global HTTP middleware stack.
|
||||
*
|
||||
* These middleware are run during every request to your application.
|
||||
*
|
||||
* @var array<int, class-string|string>
|
||||
*/
|
||||
protected array $middleware = [
|
||||
// \App\Http\Middleware\TrimStrings::class,
|
||||
// \App\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
\Hypervel\Session\Middleware\StartSession::class,
|
||||
\App\Http\Middleware\CheckMaintenanceMode::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware groups.
|
||||
*
|
||||
* @var array<string, array<int, class-string|string>>
|
||||
*/
|
||||
protected array $middlewareGroups = [
|
||||
'web' => [
|
||||
// \Hypervel\Router\Middleware\SubstituteBindings::class,
|
||||
// \Hypervel\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
// \Hypervel\View\Middleware\ShareErrorsFromSession::class,
|
||||
// \App\Http\Middleware\VerifyCsrfToken::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
// 'throttle:60,1,api',
|
||||
// \Hypervel\Router\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's middleware aliases.
|
||||
*
|
||||
* Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
|
||||
*
|
||||
* @var array<string, class-string|string>
|
||||
*/
|
||||
protected array $middlewareAliases = [
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
'can' => \Hypervel\Auth\Middleware\Authorize::class,
|
||||
'throttle' => \Hypervel\Router\Middleware\ThrottleRequests::class,
|
||||
'bindings' => \Hypervel\Router\Middleware\SubstituteBindings::class,
|
||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||
'ultimate' => \App\Http\Middleware\EnsureUserIsUltimate::class,
|
||||
'decodeRouteArgument' => \App\Middleware\DecodeRouteArgumentMiddleware::class,
|
||||
'module' => \App\Http\Middleware\CheckModuleEnabled::class,
|
||||
'abilities' => \App\Http\Middleware\CheckTokenAbilities::class,
|
||||
'token.ip' => \App\Http\Middleware\EnforceTokenIp::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The priority-sorted list of middleware.
|
||||
*
|
||||
* Forces non-global middleware to always be in the given order.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $middlewarePriority = [
|
||||
\Hypervel\Router\Middleware\ThrottleRequests::class,
|
||||
\Hypervel\Router\Middleware\SubstituteBindings::class,
|
||||
\Hypervel\Session\Middleware\StartSession::class,
|
||||
\Hypervel\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
];
|
||||
}
|
||||
16
app/Http/Middleware/Authenticate.php
Normal file
16
app/Http/Middleware/Authenticate.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Hypervel\Auth\Middleware\Authenticate as Middleware;
|
||||
use Closure;
|
||||
|
||||
class Authenticate extends Middleware
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
36
app/Http/Middleware/CheckMaintenanceMode.php
Normal file
36
app/Http/Middleware/CheckMaintenanceMode.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use App\Http\Controllers\Helpers\ResponseHelper;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Hypervel\Support\Facades\Redis;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use App\Enums\UserTypes;
|
||||
|
||||
class CheckMaintenanceMode
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request, Closure $next): ResponseInterface
|
||||
{
|
||||
$isMaintenance = Redis::get('system:maintenance_mode') === 'true';
|
||||
|
||||
if ($isMaintenance) {
|
||||
$user = Auth::user();
|
||||
|
||||
// Allow Ultimate users to bypass maintenance mode
|
||||
if (!$user || $user->acct_type !== UserTypes::ULTIMATE) {
|
||||
// Return 503 Service Unavailable
|
||||
return ResponseHelper::returnError('System is currently under maintenance. Transactions are temporarily disabled. Please try again later.', 503);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
49
app/Http/Middleware/CheckModuleEnabled.php
Normal file
49
app/Http/Middleware/CheckModuleEnabled.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Support\ModuleHelper;
|
||||
use Closure;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Middleware: module:<key>
|
||||
*
|
||||
* Blocks the request with a 403 JSON response when the specified module
|
||||
* is disabled in config/modules.php (driven by MODULE_<KEY>_ENABLED env).
|
||||
*
|
||||
* Usage in routes:
|
||||
* Route::post('/api/pos/start', ..., ['middleware' => 'module:pos']);
|
||||
*
|
||||
* Or applied to a group:
|
||||
* Route::group(['middleware' => 'module:products'], function () { ... });
|
||||
*/
|
||||
class CheckModuleEnabled
|
||||
{
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param Closure $next
|
||||
* @param string $moduleKey The module key from config/modules.php
|
||||
* @return ResponseInterface|mixed
|
||||
*/
|
||||
public function handle($request, Closure $next, string $moduleKey = '')
|
||||
{
|
||||
if ($moduleKey && ModuleHelper::isDisabled($moduleKey)) {
|
||||
$label = ModuleHelper::getLabel($moduleKey);
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => "The \"{$label}\" module is currently disabled.",
|
||||
'module' => $moduleKey,
|
||||
'code' => 'MODULE_DISABLED',
|
||||
], 403);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
56
app/Http/Middleware/CheckTokenAbilities.php
Normal file
56
app/Http/Middleware/CheckTokenAbilities.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Auth\BearerTokenResolver;
|
||||
use Closure;
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Middleware: abilities:<ability>[,<ability>...]
|
||||
*
|
||||
* Passes when:
|
||||
* - request is session-authenticated (no token in play), OR
|
||||
* - the bearer token has ALL listed abilities (or wildcard '*').
|
||||
*
|
||||
* Note: this does not replace your RBAC role check; pair with a permission
|
||||
* check on the underlying user where appropriate.
|
||||
*/
|
||||
class CheckTokenAbilities
|
||||
{
|
||||
public function handle($request, Closure $next, string ...$abilities): ResponseInterface
|
||||
{
|
||||
$user = Auth::user();
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Unauthenticated.',
|
||||
'code' => 'UNAUTHENTICATED',
|
||||
], 401);
|
||||
}
|
||||
|
||||
$token = BearerTokenResolver::current();
|
||||
|
||||
// Session auth (no token) — abilities are not enforced at this layer.
|
||||
if ($token === null) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
foreach ($abilities as $ability) {
|
||||
if (! $token->can($ability)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => "Token missing ability: {$ability}",
|
||||
'code' => 'MISSING_ABILITY',
|
||||
'required' => $ability,
|
||||
], 403);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
18
app/Http/Middleware/ConvertEmptyStringsToNull.php
Normal file
18
app/Http/Middleware/ConvertEmptyStringsToNull.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Hypervel\Foundation\Http\Middleware\ConvertEmptyStringsToNull as Middleware;
|
||||
|
||||
class ConvertEmptyStringsToNull extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the attributes that should not be transformed to null.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected array $except = [
|
||||
];
|
||||
}
|
||||
38
app/Http/Middleware/EnforceTokenIp.php
Normal file
38
app/Http/Middleware/EnforceTokenIp.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Auth\BearerTokenResolver;
|
||||
use Closure;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Middleware: token.ip
|
||||
*
|
||||
* If the current request authenticated via a personal access token,
|
||||
* enforce the token's allowed_ips list. Session-authenticated requests
|
||||
* pass through untouched.
|
||||
*/
|
||||
class EnforceTokenIp
|
||||
{
|
||||
public function handle($request, Closure $next): ResponseInterface
|
||||
{
|
||||
$token = BearerTokenResolver::current();
|
||||
|
||||
if ($token !== null) {
|
||||
$ip = BearerTokenResolver::clientIp($request);
|
||||
if (! $token->ipAllowed($ip)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Request IP not allowed for this token.',
|
||||
'code' => 'IP_NOT_ALLOWED',
|
||||
], 403);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
43
app/Http/Middleware/EnsureUserHasRole.php
Normal file
43
app/Http/Middleware/EnsureUserHasRole.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Hyperf\HttpMessage\Exception\UnauthorizedHttpException;
|
||||
use Hyperf\HttpServer\Annotation\Middleware;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
use App\Enums\UserTypes;
|
||||
|
||||
class EnsureUserHasRole extends Middleware
|
||||
{
|
||||
/**
|
||||
* Process an incoming server request.
|
||||
*
|
||||
* @param ServerRequestInterface $request
|
||||
* @param Closure $next
|
||||
* @param string ...$roles
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request, Closure $next, string...$roles): ResponseInterface
|
||||
{
|
||||
/** @var \App\Models\User $user */
|
||||
$user = Auth::user();
|
||||
|
||||
if (!$user) {
|
||||
throw new UnauthorizedHttpException('Unauthorized: Please login.');
|
||||
}
|
||||
|
||||
foreach ($roles as $role) {
|
||||
if ($user->hasRole($role)) {
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
throw new UnauthorizedHttpException('Unauthorized: You do not have the required role.');
|
||||
}
|
||||
}
|
||||
32
app/Http/Middleware/EnsureUserIsUltimate.php
Normal file
32
app/Http/Middleware/EnsureUserIsUltimate.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Hyperf\HttpMessage\Exception\UnauthorizedHttpException;
|
||||
use Hyperf\HttpServer\Annotation\Middleware;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
use Hypervel\Support\Facades\Auth;
|
||||
|
||||
use App\Enums\UserTypes;
|
||||
|
||||
class EnsureUserIsUltimate extends Middleware
|
||||
{
|
||||
/**
|
||||
* Process an incoming server request.
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request, Closure $next): ResponseInterface
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if (!$user || $user->acct_type !== UserTypes::ULTIMATE) {
|
||||
throw new UnauthorizedHttpException('', 'Unauthorized: Only ultimate users allowed.');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
29
app/Http/Middleware/InertiaMiddleware.php
Normal file
29
app/Http/Middleware/InertiaMiddleware.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
// use Hyperf\HttpServer\Contract\ResponseInterface;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
|
||||
|
||||
|
||||
class InertiaMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function process($request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
$response = $handler->handle($request);
|
||||
|
||||
if ($request->hasHeader('X-Inertia')) {
|
||||
return $response
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withHeader('X-Inertia', 'true');
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
18
app/Http/Middleware/TrimStrings.php
Normal file
18
app/Http/Middleware/TrimStrings.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Hypervel\Foundation\Http\Middleware\TrimStrings as Middleware;
|
||||
|
||||
class TrimStrings extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the attributes that should not be trimmed.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected array $except = [
|
||||
];
|
||||
}
|
||||
24
app/Http/Middleware/ValidateSignature.php
Normal file
24
app/Http/Middleware/ValidateSignature.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Hypervel\Router\Middleware\ValidateSignature as Middleware;
|
||||
|
||||
class ValidateSignature extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the query string parameters that should be ignored.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected array $except = [
|
||||
// 'fbclid',
|
||||
// 'utm_campaign',
|
||||
// 'utm_content',
|
||||
// 'utm_medium',
|
||||
// 'utm_source',
|
||||
// 'utm_term',
|
||||
];
|
||||
}
|
||||
18
app/Http/Middleware/VerifyCsrfToken.php
Normal file
18
app/Http/Middleware/VerifyCsrfToken.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Hypervel\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
|
||||
|
||||
class VerifyCsrfToken extends Middleware
|
||||
{
|
||||
/**
|
||||
* The URIs that should be excluded from CSRF verification.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected array $except = [
|
||||
];
|
||||
}
|
||||
30
app/Http/Requests/DemoRequest.php
Normal file
30
app/Http/Requests/DemoRequest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Hypervel\Foundation\Http\FormRequest;
|
||||
|
||||
class DemoRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|min:3',
|
||||
'email' => 'required|email',
|
||||
'email2' => 'required|array',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user