initial: bootstrap from BukidBountyApp base

This commit is contained in:
Jonathan Sykes
2026-06-06 18:43:00 +08:00
commit eb4a5731fb
5674 changed files with 160857 additions and 0 deletions

55
ai-docs/ai-guide.md Normal file
View File

@@ -0,0 +1,55 @@
# AI Assistant Navigation Guide
## Reading Strategy
### Start Here
1. **repo-overview.md** - Understand the project purpose, technologies, and high-level structure
2. **architecture.md** - Learn module interactions and data flows
### Locating Code
3. Use **file-map.json** to find which module contains your target file
4. Use **function-index.json** to locate specific functions/classes by name
### Reading Details
5. Read the corresponding **module/*.md** for component-level understanding
6. Read **file/*.md** for implementation details
### When to Open Source Files
Only open original source files when:
- Implementation details are not documented
- Debugging requires exact line numbers
- Understanding edge cases not covered in documentation
## Quick Reference
| Task | Best Approach |
|------|---------------|
| Find login logic | Check `LoginController` in function-index.json, then read `app/Http/Controllers/LoginController.php` |
| Add new user role | Modify `app/Enums/UserTypes.php`, update permissions in `UserPermissions` |
| Create new product API | Add route in `routes/web.php`, create controller in `app/Http/Controllers/Market/` |
| File upload handling | Use `FilesMainController@UploadFilefromRequest` |
## Common Operations
### Adding a New Page
1. Update `file-map.json` to add file mapping
2. Create Vue component in `resources/js/Pages/`
3. Add route in `routes/web.php`
4. Create controller if needed
5. Update viewmap config if server-side rendering required
### Modifying User Permissions
1. Check `UserActions` enum in `app/Enums/UserActions.php`
2. Review `UserPermissions::isActionPermitted()` logic
3. Update role matrix as needed
### Debugging API Endpoints
1. Find route in `routes/web.php`
2. Locate controller from function-index.json
3. Check middleware requirements in `Kernel.php`
## Architecture Tips
- **Vue Router**: SPA routes handled via Vue's client-side routing
- **Server Rendering**: Use `/p/{page}/s/` for server-rendered pages
- **Permissions**: Role checking happens via `canDo()` method on User model

90
ai-docs/architecture.md Normal file
View File

@@ -0,0 +1,90 @@
# Architecture
## Major Modules
### User Management Module
- **Purpose**: Handles user authentication, roles, and permissions
- **Key Components**:
- `User` model with hierarchical relationships (parent/children)
- 13 user types via enum (Ultimate through Public)
- Permission system via `UserActions` enum
- **Location**: `app/Models/User.php`, `app/Enums/UserTypes.php`
### Market Module
- **Purpose**: Product and store management
- **Key Components**:
- `Product` model - Products with categories, stores relationship
- `Store` model - Stores with products via belongsToMany
- `ProductTransaction` models - Transaction tracking
- **Location**: `app/Models/Market/`
### File Management Module
- **Purpose**: File upload and management system
- **Key Components**:
- `FileContent` model - Binary file storage
- `FileList` model - File metadata
- **Location**: `app/Models/FileContent.php`, `app/Models/FileList.php`
## Module Interactions
```
User (Auth) → Middleware → Controllers → Models
Market Models
File Management
```
### Authentication Flow
1. User attempts to access protected route
2. `Authenticate` middleware checks session/JWT
3. User object loaded with roles and permissions
4. Route middleware (`auth`, `ultimate`, etc.) validates access
## Data Flows
### Login Flow
```
POST /post/loginnow → LoginController@authenticate
Auth::attempt() (JWT-based)
Session created via Hypervel\Session
Redirect to home or return JSON response
```
### User Creation Flow
```
POST /admin/user/create → CreateUserControllerUltimate@CreateUser
Validate data (mobile number, username uniqueness)
Create user record with encrypted password
Return success/error response
```
### Product View Flow
```
POST /View/Product/Details/data → ProductController@viewProductDetails
Fetch product by ID
Fetch associated stores
Return product data with store info
```
## External Services
- **Redis**: Caching layer (configurable via CACHE_DRIVER)
- **Session Storage**: Database-backed sessions
- **File Storage**: Local filesystem (via Flysystem)
## Architectural Patterns
- **MVC Pattern**: Models-Views-Controllers separation
- **SPA Architecture**: Single Page Application with Vue Router
- **Middleware Chain**: Request filtering via Hypervel middleware
- **Enum-based Roles**: Type-safe user role system

View File

@@ -0,0 +1,116 @@
# Store Owner Audit — 2026-05-14 19:01:10 UTC
Running notes from the Store Owner accessible-pages audit. Bugs we
have already fixed are listed first for traceability; the open items
below are not yet patched and need a follow-up decision before merge.
## Fixed (commit b4defbe + this batch)
### Erratic table names (Hypervel surfaces these as empty 400s)
- `app/Http/Controllers/Market/ProductController.php:766`
`pluck('stores.hashkey')``pluck('str.hashkey')`. Drove the
Manage Listings modal in `ManageProductsAdmin.vue` to fail on every
open.
- `app/Http/Controllers/Market/BatchController.php:91`
`where('products.id', …)``where('prd_items.id', …)`. Broke the
"already attached to this store" check in batch-import (existing
mode).
### Validation/permission walls
- `ProductController::editProductAdmin` required `ModifyAllProducts`
(Big-3 only) even when `data.store_hash` was supplied. STORE_OWNER
edits via `UpdateProductModal.vue` returned 401. Now per-store
edits only need `ModifyOwnProduct` / `AddProducttoOwnStore` plus
the existing ownership branch.
- `ProductController::toggleProductStatus_Admin` — same gate, same
relaxation.
- `ProductController::deleteProduct_Admin` required
`DeleteAllProducts` (Big-3 only). Now accepts `DeleteOwnProduct`
and the ancestor-of-creator path so owners can delete products
they created.
- `PosController::voidSession` had no authorization check at all.
Any session_hash could void any session. Now gated by
`UserPermissions::isUserAllowedAccessToStore`.
- `PosAccessKeyController::destroy` and `toggleStatus` only checked
the action permission. Now also verify the key belongs to a store
the caller owns/manages.
- `StoreController::autoCreate` had no RBAC. Now requires
Big-3 or STORE_OWNER, and dedupes generated names against the
globally unique `str.name` index by appending a short suffix.
- `HomeStoreOwner.vue` referenced an undefined `creatingStore` ref
(the actual ref is `creatingQuickStore`) — the "Creating your
store..." spinner never rendered.
### BatchAddProducts opened to STORE_OWNER with guardrails
- `BatchController::batchCreateProducts` now allows STORE_OWNER, but
only when `target_store_hash` is provided AND the target store is
owned (or managed) by the caller. Without it the call is rejected
up-front instead of silently 401-ing partway through the loop.
- New-mode rows are rejected when a global product with the same
name (case-insensitive, trimmed) already exists. Owners are
expected to pick the existing one via the fuzzy-search modal.
- `BatchAddProducts.vue` now redirects STORE_OWNER to CreateStore via
a yes/no modal when they have zero selectable stores, and refuses
submit if no target store is picked.
## Open — not yet patched, decide before merging
### `editStoreDetails` data leak (medium)
- File: `app/Http/Controllers/Market/StoreController.php:455`
- Route: `POST /Edit/Store/Details/data`
(`routes/web.php:468-474`, middleware `auth + module:stores`).
- Behavior: looks up a `str` row by `hashkey`, and returns
`name`, `category`, `subcategory`, `description`, `address`,
`remarks`, `status`, `is_active`, `photourl`,
`owner.hashkey`, `manager.hashkey`, all `store_managers` user
hashkeys, all linked cooperative hashkeys, the parent-user
selector list, and the dropzone payload — **without** any
authorization check against the caller. Any authenticated user
can issue this POST with any store's hashkey and get back the
owner/manager/coop graph plus internal remarks. There is no
modification path here, so this is information disclosure rather
than escalation, but `remarks` is explicitly marked internal in
the CreateStore flow and the manager/cooperative hashkeys leak
the org graph.
- Suggested fix: require Big-3 OR
(`store->owner_id`/`manager_id` matches caller)
OR caller appears in `store_managers` pivot
OR caller is ancestor of any of the above
(the same predicate used by `canUserAccessPos` /
`isUserAllowedAccessToStore`). Strip `remarks` from the response
for non-Big-3.
- Status: **NOT FIXED IN THIS COMMIT.** Awaiting decision on
whether to (a) tighten the endpoint and accept the chance of
breaking any external/legacy caller, or (b) keep the
read-anybody behavior and only strip `remarks`.
### Other observations (not bugs strictly)
- `editProductAdminByStore` (`ProductController.php:494`) has a
`TODO Check first if store_id is owned by current user` comment
and modifies the global product directly. Currently nothing
routes to it, but leaving it in the codebase is a footgun if
someone wires it up later. Recommend deleting.
- `listProducts_Admin` scopes non-Big-3 by `created_by`, so a
store owner who attached a Big-3-created global product into
their store cannot see/manage that product from "My Products".
Per dictionary L265 this is intentional, but it does mean
store-listings management for owners is split between
"My Products" (creator-scoped) and the per-store flows
(`AddProductsToStore`, `ManageStoresAdmin`).
- The Store Owner home's "Import Products" tile (`pagename:
BatchAddProducts`) now works for STORE_OWNER thanks to the
BatchController relaxation above. Pre-fix this would have
loaded the page and then 401-ed on submit.
## Stores accessed during this audit
- `StoreController` (full file)
- `ProductController` (full file)
- `BatchController` (lines 33172)
- `PosController` (sample around startSession/voidSession/getTodayStats)
- `PosAccessKeyController` (full file)
- Pages: `HomeStoreOwner.vue`, `CreateProductStoreOwner.vue`,
`CreateStore.vue`, `AddProductsToStore.vue`, `BatchAddProducts.vue`,
`ManageProductAdmin.vue`, `ManageProductsAdmin.vue`,
`ManageStoresAdmin.vue`, `PosAccessKeys.vue`,
`UpdateProductModal.vue`
- Routes: `routes/web.php` (home-data + Store/Product/POS blocks)

86
ai-docs/call-graph.md Normal file
View File

@@ -0,0 +1,86 @@
# Call Graph
## Authentication Flow
```
POST /post/loginnow → LoginController@authenticate
Auth::attempt()
Session::start()
Redirect or JSON response
```
## User Creation Flow
```
POST /admin/user/create → CreateUserControllerUltimate@CreateUser
validateMobileNumberExists() / validateUsernameExists()
HashPassword()
User::create()
Return success/error
```
## User Management Flow
```
POST /admin/user/details → UserModifyAdminPageController@Response_UserDetails
User::find($id)
Return user details with relationships
```
## Login Flow (SPA)
```
GET /login → viewHelperController@servePageFragmentWithTemplate
view('Auth.Login', $data)
Vue renders login form
POST /post/loginnow
```
## Product View Flow
```
POST /View/Product/Details/data → ProductController@viewProductDetails
Product::find($id)
Store::where('products.product_id')
Return product with store data
```
## Market Operations Flow
```
POST /Products/Admin/New/ → ProductController@createNew_Admin
Product::create()
Return product data
```
## File Upload Flow
```
POST /File/Upload/{category} → FilesMainController@UploadFilefromRequest
Storage::put('files', $file)
FileContent::create($data)
Return file hash key
```
## Application Startup
```
resources/js/app.js → createApp()
Pinia.createPinia()
useUserStore().fetchCurrentUser()
mount('#app')

View File

@@ -0,0 +1,76 @@
{
"app/Http/Controllers/viewHelperController.php": {
"module": "pages",
"imports": [
"app/Http/Controllers/Pages/UserModifyAdminPageController.php"
],
"exports": []
},
"app/Http/Controllers/UserCreateController.php": {
"module": "userManagement",
"imports": [
"app/Http/Controllers/Helpers/Permissions/UserPermissions.php"
],
"exports": []
},
"app/Http/Controllers/Market/ProductController.php": {
"module": "market",
"imports": [
"App\\Enums\\UserActions",
"App\\Models\\Market\\Product",
"App\\Models\\Market\\Store",
"App\\Http\\Controllers\\Helpers\\Permissions\\UserPermissions"
],
"exports": []
},
"app/Http/Controllers/Market/StoreController.php": {
"module": "market",
"imports": [
"App\\Models\\Market\\Product",
"App\\Models\\Market\\Store",
"App\\Models\\User"
],
"exports": []
},
"app/Http/Controllers/Pages/UserModifyAdminPageController.php": {
"module": "userManagement",
"imports": [
"App\\Enums\\UserActions",
"App\\Http\\Controllers\\Helpers\\Permissions\\UserPermissions",
"App\\Models\\User"
],
"exports": []
},
"app/Http/Controllers/Pages/UserListPageController.php": {
"module": "userManagement",
"imports": [
"App\\Models\\User"
],
"exports": []
},
"app/Http/Controllers/Pages/AccountSettingsPageController.php": {
"module": "accountSettings",
"imports": [
"App\\Models\\User",
"Hypervel\\Support\\Facades\\Auth"
],
"exports": []
},
"app/Http/Controllers/FilesMainController.php": {
"module": "files",
"imports": [
"App\\Models\\FileContent",
"App\\Models\\FileList",
"Hypervel\\Support\\Facades\\Storage"
],
"exports": []
},
"app/Http/Controllers/Pages/TransferMyCreditPageController.php": {
"module": "transferCredits",
"imports": [
"App\\Models\\User",
"Hypervel\\Support\\Facades\\Auth"
],
"exports": []
}
}

748
ai-docs/dictionary.md Normal file
View File

@@ -0,0 +1,748 @@
# Dictionary
## Database Standards
## Required Fields
- **created_by**: Every table should have this field, linked to the user ID of the creator.
- **updated_by**: Every table should have this field, linked to the user ID of the last updater.
- **is_active**: A boolean field, typically set to `true` by default, used for soft-deactivation or status tracking.
- **hashkey**: string (300), unique. A unique identifier for every record, typically generated by `App\Listeners\ModelSavingListener` using `Str::uuid() . Str::random(100)`.
## Audit Fields & Eloquent Events
- **Audit Fields**: `created_by` and `updated_by` are critical for audit trails across all modules.
- **ModelSavingListener**: `app/Listeners/ModelSavingListener.php` automatically populates `hashkey`, `created_by`, and `updated_by` for Eloquent models during the `Saving` event.
- **Raw DB Bypass**: Using `DB::table(...)->insert()` or `update()` **bypasses** Eloquent events and listeners.
- **Manual Requirement**: When using raw DB queries for performance (e.g., in `PosController.php`), you **MUST** manually include `created_by` or `updated_by` (using `Auth::id()`) and `updated_at`/`created_at` (using `now()`) in the data array to maintain data integrity and audit trails.
## UI Standards
- **Buttons and Inputs**: Prefer `.rounded-pill` for a modern, premium look unless otherwise specified.
- **Form Spacing**: Use `d-flex gap-2` or `mb-3` to ensure interactive elements are not too close, especially on mobile views.
- **Form Controls**: Standardize on `form-control` and `form-select` with `rounded-pill`.
- **Empty States**: When a list or dataset is empty, provide a centered, clear message (e.g., "No records found") and an icon (using `fad` or `fas` with `text-muted` and `opacity-2`) to guide the user.
- **Avatars**: For user profile pictures, if a custom photo is not provided (`photourl`), use `https://ui-avatars.com/api/` with the user's name as a dynamic fallback. This ensures a premium, personalized look even without an uploaded image. Avoid relying on local static placeholders like `avatar.png`.
- **Glassmorphism**: Use `backdrop-filter: blur(15px);` and `background-color: rgba(var(--bg-card-rgb), 0.7);` for floating headers and overlay components. A utility class `.glass-card` is available in `app.js` for consistent application.
- **Dark Mode Surface Isolation**: For a modern "seamless" look, avoid forced backgrounds on navigation items and service buttons (`li`, `a`). Instead, use subtle drop-shadows and icon contrast filters (`brightness`, `contrast`) to maintain hierarchy.
- **Scoped Dark Mode**: When fixing dark mode in Vue components with `<style scoped>`, use `:global(.dark-mode) .selector` to ensure styles are correctly applied when the `dark-mode` class is on the `body`.
- **Theme Variables**: Use global CSS variables defined in `app.js` (e.g., `--bg-primary`, `--bg-card`, `--text-primary`, `--accent-color`) for consistent theme-aware styling.
- **Administrative Icon Standards**: For high-density administrative dashboards (like `UltimateConsole.vue`), standardize on the following Font Awesome sizes for visual hierarchy:
- **Navigation Icons**: `fa-lg` to provide better target recognition.
- **Dashboard Stat Cards**: `fa-2x` to fill the visual container and provide focus.
- **Primary Action Buttons**: `fa-lg` (e.g., `fas fa-paper-plane fa-lg`) within buttons for a premium feel.
- **Main Dashboard Logo**: `fa-4x` for a bold, centered focus on primary consoles.
- **Bulk Tool Icons**: `fa-2x` to accentuate the tool's category and provide clear differentiation.
- **Table Actions**: `fa-lg` for critical row-level controls (Download, Delete, Edit).
- **Dark Mode Scoped Fixes**: Use `:global(.dark-mode) .selector` or theme variables (e.g., `var(--bg-primary)`) to override hardcoded colors in Vue `<style scoped>` blocks. Remove `text-dark` and `bg-white` classes where theming is required.
- **Ultimate Console Premium Header**: The administrative header uses a deep, multi-stop radial gradient (`#020617` base) with mesh highlights and intensified glassmorphism for stats pills (`badge-pill-premium`). This ensures a "Power User" feel that distinguishes administrative zones from standard user interfaces.
- **Header Action Buttons**: Use `.btn-glass-premium` for auxiliary header actions (e.g., Refresh, Filter) within colored headers. This style uses backdrop blur and subtle transparency for a sophisticated, modern look without clashing with the header background.
- **Responsive Table Actions**:
- **Container**: Use `d-flex flex-wrap gap-1` instead of `btn-group` in table action columns to prevent horizontal scrolling.
- **Text Labels**: Use `.d-none .d-lg-inline` for text labels within buttons (e.g., `<span class="d-none d-lg-inline">Manage</span>`) to save space on smaller screens while keeping the icon visible.
- **Min Width**: Apply `style="min-width: 150px;"` (or appropriate value) to the container to ensure enough space for at least a few icons without excessive wrapping.
- **Sticky Headers**: For configuration-heavy or high-density administrative pages (e.g., `SystemSettings.vue`, `UltimateConsole.vue`), use `sticky-top` on the primary header. This ensures critical actions like "Save" or "Back" remain accessible while scrolling through long forms. **CRITICAL**: Because the application uses a fixed `TopHeader`, you **MUST** provide an explicit `top` offset (e.g., `top: 66px;` or `top: var(--header-height, 66px);`) and a high `z-index` (e.g., `1020`) to ensure the sticky element sticks *below* the global navigation bar rather than being hidden behind it. Adjust sidebar `top` values accordingly (e.g., `13rem` to `18rem` depending on header height) to prevent overlap.
## Layout & Responsiveness
- **Max Width**: The application uses a dynamic maximum width for the main layout, controlled by the CSS variable `--layout-max-width`.
- **Default Width**: `1440px` (Modern Desktop/Tablet Landscape).
- **Mobile Behavior**: On screens smaller than the max width, the layout automatically scales to 100% width.
- **Centering**: The `body` and fixed elements (`TopHeader`, `BottomNav`) are centered using `margin: 0 auto`.
- **Full Width Mode**: The `.is-full-width` class on the `body` overrides the max-width to `none`, allowing components like the POS to utilize the entire screen.
- **Implementation**: Managed globally in `resources/js/app.js` via an injected `<style>` tag, and supported by `:global` overrides in scoped components.
## Migration Standards (Hypervel/Hyperf)
- **DO NOT use Illuminate classes** in migrations. This project runs on Hypervel (Hyperf + Swoole), not Laravel. Using `Illuminate\Database\Migrations\Migration`, `Illuminate\Database\Schema\Blueprint`, or `Illuminate\Support\Facades\Schema` will cause fatal class-not-found errors at runtime.
- **Correct imports for all migrations**:
```php
use Hyperf\Database\Schema\Blueprint;
use Hypervel\Database\Migrations\Migration;
use Hypervel\Support\Facades\Schema;
```
- **Why**: Hypervel wraps Hyperf's database layer. The `Illuminate` namespace does not exist in this project. The Hypervel `Migration` class provides proper coroutine-context-aware connection resolution via `ApplicationContext`.
- **Commands**: `php artisan migrate`, `php artisan migrate:rollback`, `php artisan migrate:fresh`, etc. work identically to Laravel.
# Development Requirements
## 1. API / Web Route Permissions
- For every new function exposed through an API or web route:
- Add a corresponding permission in `UserPermissions` and `UserActions`.
- Ensure the permission is validated before granting access to the function.
### Route file scope (important)
- **`routes/api.php`** is intentionally minimal. It only registers: `IndexController@index`, `RemoteLogoutController@logout`, and `Market\ActivityController` (`/activity/recent`, `/activity/search`).
- **Products, users, stores, POS, cart, cooperatives, admin, settings, landing pages, etc. are ALL defined in `routes/web.php`** — not `api.php`. Despite paths like `/api/pos/...`, `/api/public/...`, or `/admin/users/list` appearing throughout the codebase, these are web routes (Inertia/session-auth based), not REST API routes.
- When searching for product/user/store endpoints, grep `routes/web.php` first. Do not assume `/api/*` paths live in `routes/api.php`.
## 2. Vue Routing
- For every new page:
- Add a corresponding entry in `VueRouteMap`.
- Ensure the route is properly registered and accessible via the application's routing system.
- Pages are using vue not blade. Blade pages are legacy code and must not be modified but can be a source of investigation
- **Direct Page Access**: Pages such as **view** or **edit** must include one of the following in the URL:
- `h=<HASH_KEY>`
- `e=<ENCODED_PAYLOAD>`
- URL Format: `/page-name--h:HASHKEY` or `/page-name--e:PAYLOAD`
- **Product Context**: When a product is accessed from a store context (e.g., in `BuyViewProductMarket` or `ManageProductAdmin`), use an encoded payload (`--e:`) containing `product_hashkey` and `store_hashkey` to ensure context persistence and allow direct URL access.
- This ensures that:
- Pages can be accessed directly via URL.
- Functionality remains intact even without navigation state.
## POS Access Keys
- **Terminal Name**: A descriptive name for a POS terminal (e.g., "Counter 1").
- **Access Key**: A unique key used to authenticate a POS terminal without a traditional login.
- **Target File**: `resources/js/Pages/PosAccessKeys.vue` - Main management interface for POS access keys.
- **Store Hash**: The hashkey of the store associated with the access key.
- **Store Name**: Displayed in `PosMain.vue`, retrieved from `posStore.activeSession.store.name` or `posStore.todayStats.store_name`.
## Sharing Protocols
- **Web Share API**: Used for sharing content (text, URLs) via the device's native sharing dialog. Supported on most modern mobile browsers.
## Market Enums
- **TransactionFlow**: Categorizes transactions into `INCOME` (1), `EXPENSE` (-1), or `NEUTRAL` (0).
- **ProductTransactionType**: Defines specific transaction events (e.g., `ONLINE_SALE`, `PURCHASE`) which map to a `TransactionFlow`.
- **Add Transaction Page**: `resources/js/Pages/AddTransaction.vue` - Interface for manually recording global or store-specific transactions.
## Customer Management
- **Table**: `cst` (Customers)
- **Key Fields**: `hashkey`, `name`, `store_id`, `user_id`.
- **Suggestions**: Handled in `PosMain.vue` via `/api/pos/get-customers`. Employs a **Stale-While-Revalidate** pattern: immediate results from Pinia/LocalStorage cache while background fetching latest from the server.
- **Session Lifecycle**: Transactions are completed in `confirmComplete`. Upon success, the session is reset, but the terminal identity (`pos_access_key` and captured `storeHash`) is preserved. A new session is then automatically started via `posStore.startNewSession` to ensure a seamless "customer-after-customer" checkout flow. To support this, `pos_sessions` allows multiple records per `pos_access_key` (removed unique constraint).
- **Quick Select**: Displays the last 3 recently used customers as quick-pick buttons when the customer name input is empty/focused. This provides immediate selection without searching.
- **Cache**: Persisted in `localStorage` under `pos_cached_customers`.
## Routing Architecture
- **VueRouteMap**: `app/Http/Controllers/Support/VueRouteMap.php` - Centralized mapping for Vue components to routes.
- **SPA Routing**: Catch-all route in `web.php` maps to `VueRouteMap::handleSpa()`.
- **Navigation**: Uses `useNavigate` composable. Pagenames should match Vue component names (PascalCase).
- **Initial State**: `app.js` initializes history state on load using `window.history.replaceState` to ensure back/forward navigation works for the entry page.
- **Prop Extraction**: The backend automatically extracts hash/payload tokens and provides them as `target`, `hashkey`, and `id` props to the rendered Vue component.
- **URL Flexibility**: The SPA router (`VueRouteMap::handleSpa`) is robust against hyphen and case discrepancies in the URL slug. It automatically matches unhyphenated slugs (e.g., `/viewstoremarket`) and misaligned casing to their correct components (e.g., `ViewStoreMarket`) defined in the map.
- **Common Mappings**:
- `/user-list` -> `UserList.vue`
- `/manage-products` -> `ManageProductsAdmin.vue`
- `/manage-product-admin` -> `ManageProductAdmin.vue`
## Missing/Future Modules
- **Shipments**: `ShipmentList.vue` - For logistics and product tracking. Handled by `ShipmentController.php`.
- **Farmer Management**: `FarmerProfileEdit.vue` - For registering farmer profiles. Handled by `FarmerController.php`.
- **Verification Dashboard**: `VerificationDashboard.vue` - For admin review of farmer profiles. Handled by `FarmerController.php`.
## Groups & Associations
- **Module**: `Groups`
- **Tables**: `groups`, `group_members`
- **Architecture**: A generic container for various member organizations.
- **Group Types**: Dynamic (COOPERATIVE, ASSOCIATION, NGO, etc.) defined in `system_settings` under `group_types`.
- **Key Fields**: `hashkey`, `name`, `type`, `description`.
- **Membership**: Linked via `group_members` pivot table with `role` and `membership_type`.
- **Property Management**: `ListProperties.vue` - Interface for managing properties.
- **Referrals**: `ListReferrals.vue` - Interface for managing property referrals and leads.
- **Accounting**: Handles financial tracking and reporting.
- **Tables**: `accounts`, `account_transactions`
- **Controller**: `AccountingController.php`
- **Pages**:
- `AccountingDashboard.vue` (`/accounting-dashboard`) - Tree and Leaf views.
- `ListReports.vue` (`/list-reports`) - Transaction list and summaries.
- **Endpoints**:
- `POST /admin/accounting/reports` - Fetches recent transactions.
- `POST /admin/accounting/tree` - Hierarchical account tree.
- `POST /admin/accounting/leaf` - Flat list of leaf accounts.
## Server Configuration Notes
- **Nginx Headers**: Large JWT tokens and complex data trees (like accounting) require increased header and proxy buffers in Nginx.
- `large_client_header_buffers 4 32k;`
- `proxy_buffer_size 32k;`
- `proxy_buffers 8 64k;`
- **POST Requests**: Production environments (Nginx/WAF) may reject empty POST bodies with a 400 error. A global Axios interceptor in `app.js` ensures all POST requests carry at least an empty object `{}`.
## Shipment & Logistics
- **Module**: `Shipment`
- **Key Fields**: `hashkey`, `transaction_id`, `courier_id`, `status`, `tracking_number`.
- **Statuses**: `PENDING`, `PICKED_UP`, `IN_TRANSIT`, `DELIVERED`, `FAILED`, `RETURNED`.
- **Target Files**: `ai-docs/modules/shipment_module.md`
## Farmer Management
- **Module**: `FarmerProfile`
- **Key Fields**: `hashkey`, `user_id`, `organization_id`, `verification_status`.
- **Verification Statuses**: `UNVERIFIED`, `PENDING`, `VERIFIED`, `REJECTED`.
- **Target Files**: `ai-docs/modules/farmer_management_module.md`
## Redis Configuration
- **Connection**: `REDIS_HOST`, `REDIS_PORT`, `REDIS_DB`.
- **Authentication**: `REDIS_AUTH` or `REDIS_PASSWORD`.
- **Support**: Empty passwords are supported by setting `REDIS_AUTH=` in `.env`. The configuration uses `(?: null)` logic to handle empty strings correctly.
- **Config Files**: `config/database.php` and `config/autoload/redis.php`.
## Query Caching (Redis)
- **Helper**: `App\Http\Controllers\Helpers\CacheHelper.php`
- **Key Format**: `querycache:[queryhash]++++[parameterhash]`
- **Methods**:
- `get_cache($query, $params = [])`: Fetches from cache via `Cache` facade. Supports SQL strings or Builder objects.
- `set_cache($query, $data, $params = [], $ttl = 86400)`: Sets in cache via `Cache` facade inside a **fire-and-forget** coroutine.
- `erase_cache($query, $params = [])`: Deletes the specific cache key using `Cache::forget`.
- **Implementation**: Uses `Hypervel\Support\Facades\Cache` for better integration with the application's caching layer.
- **Async Logic**: `set_cache` uses `Hyperf\Coroutine\Coroutine::create` to return responses immediately without waiting for cache network I/O.
- **TTL**: Default is 1 day (86400 seconds).
## User Settings & UI Persistence
- **Composable**: `resources/js/composables/useUserSettings.js`
- **Controller**: `app/Http/Controllers/Market/UserSettingsController.php`
- **Frontend Sync**: Handled via `uiStore.syncSettings(settings)` which is called after `userStore.fetchCurrentUser()` in `app.js`.
- **Purpose**: Dynamically store and retrieve user-specific UI preferences in the `users.settings` JSON column.
- **Key Settings**:
- `pos_layout`: `landscape` or `regular`. Controls the split-screen behavior in `PosMain.vue`.
- `dark_mode`: Boolean. Globally toggles the application's dark theme by applying the `.dark-mode` class to the `body`.
- `pos_sound_effects`: (Future) Boolean to toggle audible feedback during scanning.
## Global Theme & Dark Mode
- **Class**: `.dark-mode` - Applied to the `<body>` element.
- **Persistence**: Saved in the `users.settings` JSON column. Included in initial user data fetch via `AccountSettingsPageController@listDetails`.
- **Implementation**: Handled via state in `uiStore` and a global reactive `watch` in `app.js` with `{ immediate: true }`.
- **UI Toggle**: Located in `AccountSettings.vue`, calls `/UserSettings/Update`.
- **Color Overrides**: Global text color overrides for dark mode are defined in `app.js` and apply to standard typography tags (`h1-h6`, `span`, `p`, `label`, `i`) and generic `div` elements, ensuring uniform color contrast across the application.
## POS & Terminal Management (Advanced)
- **"Start New Session" requires store context**: `startNewSessionSilently` (in `usePosSession.js`) guards against calling the API when both `storeHash` and `access_key` are absent. When neither is available (e.g. navigating to `/pos-main` without a URL hash or stored key), it sets `posStore.error` and returns false instead of hitting `/api/pos/start`. `PosController::startSession` now returns 422 (not 404) when no store can be resolved, with a descriptive message. `posStore.error` is surfaced in the cart area of `PosMain.vue`.
- **Landscape Mode**: Optimized for tablet/desktop usage with a split product/cart view.
- **QR/Barcode Scanner**: Integrated via `html5-qrcode` in `PosMain.vue`. Supports scanning product hashkeys, barcodes, or store-specific QR codes.
- **Session Auto-Start & Access Keys**: POS sessions are automatically restarted after completion. To prevent authorization failures, the system distinguishes between permanent terminal access keys (`PK-` prefix, stored in `pos_access_keys`) and temporary session-specific random tokens. The frontend (`pos.js`) ensures only valid terminal keys are persisted to `localStorage` (via `PosMain.vue`), and the backend (`PosController.php`) now allows authenticated users to start a session by specifying a `store_hash` even if an invalid or unrecognized `access_key` is present in the request payload. This prevents 401 errors caused by session-specific tokens being used as terminal credentials.
- **Clear All**: A "Clear All Items" feature is available in `PosMain.vue` and `pos.js` which performs a bulk deletion of all transactions in the current session via `/api/pos/clear`. This includes a database update and a session archive entry for auditing.
### File Uploads
- **Route**: `POST /File/Upload/{category}` → `FilesMainController::UploadFilefromRequest` ([app/Http/Controllers/FilesMainController.php](app/Http/Controllers/FilesMainController.php)).
- **Permission scoping**: gated by `canUploadCategory($category)` — allows anyone with `UploadAllFiles` (ULTIMATE / SUPER_OPERATOR). For `ProductMarket`, also allows roles with `CreateProductForOwnStore` or `AddProducttoOwnStore` via `ProductPermissions::isActionAllowed` (covers STORE_OWNER, STORE_MANAGER, OPERATOR). All other categories require `UploadAllFiles`.
- **Frontend**: `resources/js/composables/useFileUpload.js` posts to `/File/Upload/${category}`. The `CreateProductStoreOwner` wizard uses `category: 'ProductMarket'`.
### RBAC & Permissions Logic
- **Core Controller**: `app/Http/Controllers/Helpers/Permissions/UserPermissions.php`
- **Actions Enum**: `app/Enums/UserActions.php` (Currently defines 103 user actions).
- **Default Permissions**: ULTIMATE users are automatically granted all actions defined in `UserActions::cases()`.
- **Roles Method**: `UserPermissions::roles()` defines the default permission mapping per user type.
- **Roles with No Target User**: `UserPermissions::$RoleswithNoTargetUser` is a collection for actions that don't target a specific user (e.g., `ViewAllUserTypes`, `ViewShipments`, `UltimateConsole`).
- **User Types**: `ULTIMATE` (all access), `SUPER_OPERATOR` (most management), `OPERATOR` (limited operational access), `COORDINATOR`, `SUPPLIER_OVERSEER`, `WHOLESALE_BUYER`, `SUPPLIER`, `STORE_OWNER`, `STORE_MANAGER`, `USER`, `RIDER`, `AUDIT`, `POS_TERMINAL`.
- **Allowed User Types (Creation Hierarchy)**: Defined in `UserPermissions::UserTypeService::getAllowedUserTypes()`.
- **ULTIMATE**: Can manage ALL types.
- **SUPER_OPERATOR**: management of all entities except ULTIMATE accounts. Includes global store and POS key management. Now includes `ViewAccountingReports` and `ManageAccounting` to allow access to the Accounting Dashboard.
- **OPERATOR**: management of multiple stores, farmers, and logistics (Shipments/POS Reports). Now includes global store and product management, POS access keys, and accounting management (`ViewAccountingReports`, `ManageAccounting`).
- **STORE_OWNER**: Can manage `STORE_MANAGER`, `RIDER`, `POS_TERMINAL`.
- **RIDER**: Has no default management permissions.
## User Management
- **Create User**:
- **Validation**: All required fields (`mobile_number`, `username`, `name`, `fullname`, `type`, `password`) must show real-time validation status.
- **Field Conflicts**: Conflicts (e.g., "Mobile number already taken" or "Username already taken") must be checked via API and displayed as inline error messages.
- **Type Selection**: Filtered based on the current user's `acct_type` via `UserTypeService::getAllowedUserTypes()`.
- **UX**: The "Create Account" button should remain visible but disabled until all requirements are met. A "Remaining Requirements" summary box should appear if the user has started filling the form but missed some fields.
- **Table**: `users`
- **Controller**: `app/Http/Controllers/UserManagement/CreateUserControllerUltimate.php`
- **Page**: `resources/js/Pages/CreateUser.vue`
- **User List API**: `/admin/users/list` returns an object `{ success: true, users: [...] }` containing all visible descendants of the current user.
## Cooperative & User Profile
- **UserInfo**: Detailed personal information for users (firstname, lastname, bank details, addresses). Linked to `users` via `user_id`.
- **Table**: `user_infos`
- **Edit Page**: `resources/js/Pages/UserInfoEdit.vue`
- **Navigation Fix (April 2026)**: Accessing the profile without a `target` prop now defaults to the currently logged-in user's profile by fetching the hashkey from the Pinia store.
- **Cooperative**: An organization of type `COOPERATIVE`. Users are linked via `cooperative_members`. Data is stored in the `organizations` table.
- **Key Fields**: `registration_number` (CDA COR), `cin` (Cooperative Identification Number), `tin` (BIR TIN), `cooperative_type`, `cooperative_category`, `registration_date`, `contact_person`, `contact_number`, `contact_email`, `compliance_status`.
- **Types**: Credit, Consumer, Producer, Marketing, Service, Multi-purpose, Advocacy, Agrarian Reform, Cooperative Bank, Dairy, Education, Electric, Financial Service, Fishermen, Health Service, Housing, Insurance, Transport, Water Service, Workers.
- **Categories**: Micro (Assets <= P3M), Small (Assets > P3M to P15M), Medium (Assets > P15M to P100M), Large (Assets > P100M).
- **Member Table**: `cooperative_members`
- **List Page**: `resources/js/Pages/CooperativeList.vue`
- **Create Page**: `resources/js/Pages/CreateCooperative.vue`
- **Detail Page**: `resources/js/Pages/CooperativeDetail.vue`
- **Model**: `app/Models/Market/Organization.php`
- **Controller**: `app/Http/Controllers/Market/CooperativeController.php`
## Home Store Owner Dashboard
- **Component**: `resources/js/Pages/Fragments/Home/HomeStoreOwner.vue` - Used by `Home.vue` for `STORE_OWNER` accounts.
- **Balance Card Stats**: `transactions_today_no` (count) and `cash_flow_today_php` (signed `SUM(amount * flow)` for today, scoped to stores where the user is owner or in `store_managers`). Plus `my_stores_no` (count of stores owned by the current user).
- **Backend**: Stats are added to `GET /home-data` (`routes/web.php`), scoped by store ownership for non-Big3.
- **Buttons**: Create Store (opens a yes/no chooser modal — "Quick Create" calls `POST /Store/AutoCreate`, "Custom Create" navigates to `CreateStore.vue` with store-owner-only fields hidden), Import Products (`BatchAddProducts`), New Product (`CreateProductStoreOwner` — wizard-style flow, see below), My Products (`ManageProductsAdmin` — backend `listProducts_Admin` already scopes non-Big3 to products created by the user + descendants), POS Keys (`PosAccessKeys`).
- **Store Owner Create Product Flow** (`CreateProductStoreOwner.vue`): 4-step wizard tailored for store owners (replaces `CreateProductUltimate` on the Store Owner home). On mount, fetches `/Admin/Stores/Selectable`; if empty, opens a `yesNoModal` directing the user to `CreateStore` and blocks. Step 1: live fuzzy search via `/Products/Admin/FuzzySearch`; user picks an existing global product, OR clicks "My product is not listed — Create new" to enter Step 2-new. Step 2 (existing): description override (per-store). Step 2 (new): full global product form (name/desc/category/subcategory/price/unit/available/barcode/photos). Step 3: multi-select stores from the selectable list. Step 4: per-store rows with editable `price` and `available` (defaults seeded from global). Submit: for "new" mode, first `POST /Products/Admin/New/` with `TargetStore` = first selected store (required because store owners lack `CreateProductGlobal`), then iterate every selected store and `POST /Products/AssignToStore/` passing `target`, `TargetStore`, `price`, `available`, `description`. **`AssignProductToOwnStore` now upserts the pivot** (`updateExistingPivot` if already attached, else `attach`) and accepts an optional `description` field written to the `prd_str` pivot.
- **Auto-Create Store**: `StoreController@autoCreate` → `POST /Store/AutoCreate`. Quick path used by the Store Owner home's "Quick Create" choice. Creates a store with sensible defaults and self-assigns owner/manager + a `store_managers` row.
- **CreateStore for STORE_OWNER**:
- Hidden fields: Store Owner dropdown, Internal Remarks, Linked Cooperatives, Store Status. Owner is forced to the current user; status forced to `active`; remarks cleared.
- Additional Store Managers list: only shown when descendants of the current user with `acct_type = STORE_MANAGER` exist. Backend (`StoreController@store`) re-validates that each submitted manager hashkey is both a descendant AND a STORE_MANAGER — anything else is silently dropped.
- Self-assignment: STORE_OWNER is automatically inserted into `store_managers` for the new store, in addition to `owner_id`/`manager_id`.
- Name uniqueness: `stores.name` is validated `unique` globally. Violations return 422 with message "A store with this name already exists. Please choose a different name."
- RBAC: only Big3 + STORE_OWNER may hit `POST /Store/New`; STORE_MANAGER cannot create (only edit assigned stores).
- **User list filter (`POST /admin/user/list/numbers/hash`)**: accepts an optional `type` (string or array of `acct_type` values, e.g. `"store manager"`) to restrict the descendants-only result set. Response now includes `acct_type` per row.
- **Route Map Adjustments**: `/manage-stores`, `/batch-add-products`, and `/manage-products` now allow `store owner` (and `store manager` for `/manage-stores` and `/manage-products`) so the Store Owner home buttons land on accessible pages.
- **`/create-product-store-owner` route**: mapped in `VueRouteMap.php` → `CreateProductStoreOwner`, allowed for `ult`, `super operator`, `operator`, `store owner`, `store manager`. Required so direct URL-bar access / page reload works (in-app navigation via `useNavigate` push-states this URL).
- **Add Products to Store flow (`AddProductsToStore.vue`)**: two-step wizard at `/add-products-to-store--h:<store_hashkey>` (allowed for Big3 + `store owner` + `store manager`). Step 1: browse every active global product via `POST /Products/GlobalList` (`ProductController@listGlobalProductsForPicker` — returns all `is_active=true` products regardless of `created_by`, since picking a global product to list in your own store requires visibility into the full catalog). Multi-select card grid + free-text search over name/category/subcategory. Step 2: editable table of selected products with per-row `price` and `available`, seeded from the global defaults, plus a "Bulk apply" panel that sets every row's price or availability at once. Submit iterates `POST /Products/AssignToStore/` per row (existing upsert endpoint — see L266). Entry points: (1) `CreateStore.vue` after `POST /Store/New` success — navigates here with the new store's hashkey instead of `ListStores`; (2) `HomeStoreOwner.vue` Quick Create branch after `POST /Store/AutoCreate`; (3) `ManageStoresAdmin.vue` per-row green `+` "Add Products" action (visible when `canModifyStore(store)`).
## Home Ultimate Dashboard (Revised)
- **Component**: `resources/js/Pages/Fragments/Home/HomeUltimate.vue`
- **Style**: Maintains the "Classic" BukidBounty look with a single **ServiceButtonGrid** for primary services and a **SideTextButtonList** for secondary management actions.
- **Icons**: Standardized to use micons (`/assets/micons/`) for better dark mode visibility and modern aesthetics for key services like Products (`box-seam.svg`), Verification (`patch-check.svg`), Cooperatives (`people.svg`), and Announce (`megaphone.svg`).
- **Organization**:
- **Primary Grid**: Contains the most visual and frequently used administrative tools (Users, Stores, products, Transactions, Reports, Market, Shipments, etc.).
- **Management & Tools List**: Text-based list for specific onboarding tasks (New User/Store/Product) and personal/social features (Profile, Referrals, Properties, Credit Transfer).
- **Data Integration**: Connected to live counts for Users, Active Stores, Pending Orders, and System Balance via the updated `/home-data` endpoint.
## Product Management
- **Manage Product Admin**: `resources/js/Pages/ManageProductAdmin.vue` - Central console for managing a specific product's global and store-specific data.
- **Update Product Modal**: `resources/js/Components/Market/UpdateProductModal.vue` - A unified modal interface for editing product details, including pricing overrides for specific stores.
- **Create Product Flow** (`CreateProductUltimate.vue`): On submit, runs a fuzzy-name dedup check via `POST /Products/Admin/FuzzySearch` (`ProductController@fuzzySearchByName`, name-only LIKE + SOUNDEX + `similar_text` scoring, threshold ≥ 45). If matches exist, a modal lists them with "Import to Store" (calls `/Products/AssignToStore/` with the user's typed price/available). Otherwise — or after "None of these — Create new" — a store-picker modal always opens (per product spec) and the chosen store is sent as `TargetStore` to `/Products/Admin/New/`.
- **`.bb-modal*` styles**: shared modal styles (`.bb-modal-backdrop`, `.bb-modal`, `.bb-modal-header/body/footer/close`) are defined as **scoped** styles inside `CreateProductUltimate.vue` and `BatchAddProducts.vue`. They are NOT global — any new page using `<div class="bb-modal-backdrop">` must duplicate the CSS in its own scoped block or the modal will render invisibly.
- **Batch Add Products** (`BatchAddProducts.vue` → `POST /admin/batch/products` → `BatchController@batchCreateProducts`): Big-3 only. Top-of-page target-store picker (`/Admin/Stores/Selectable`). Each leaf is independent; default mode creates a new global product. "Pick existing" button per leaf opens a fuzzy-search modal (reuses `/Products/Admin/FuzzySearch`) to link a global product instead — if the search returns zero matches, the picker is closed and a `Warning` modal is shown. Payload per leaf: `source: 'new' | 'existing'`. For `existing`, only `product_hash`, `price`, `available`, `description` are sent; backend attaches the global product to the target store via the `prd_str` pivot, **falling back to the global's `price`/`description` when the leaf's value is blank or 0**. For `new`, the global product is created and — when a target store is picked — also attached to that store with the same price/stock/description. Pivot table `prd_str` supports `available`, `price`, `description`, `is_active` etc. (see `Store::products()` `withPivot`).
- **Store-scoped product creation permission**: `UserActions::CreateProductForOwnStore` (granted to STORE_OWNER, STORE_MANAGER, OPERATOR, SUPER_OPERATOR). `ProductController@createNew_Admin` allows the request when the caller has either `CreateProductGlobal` (Big3) or `CreateProductForOwnStore` + a `TargetStore` they own/manage; the existing per-store ownership check at the top of `createNew_Admin` enforces the latter.
- **Selectable Stores Endpoint**: `POST /Admin/Stores/Selectable` → `StoreController@listSelectableStoresForAddingProduct`. Non-Big3 query was widened from descendants-only to include self + descendants + `store_managers` pivot, so a fresh store owner sees their own store(s).
- **Manage Listings Modal (`ManageProductsAdmin.vue` store icon)**: opens a per-product modal listing the user's selectable stores. For each store it shows a "Listed" / "Not listed" badge based on `POST /Products/AssignedStores/` (`ProductController@getAssignedStoresForProduct`, returns store hashkeys the product is attached to). Each row has a List or Unlist action; List runs the existing price/availability prompt → `POST /Products/AssignToStore/`; Unlist confirms then calls `POST /Products/UnassignFromStore/` → `ProductController@RemoveProductFromStore`. `RemoveProductFromStore` was relaxed from the Big3-only `RemoveProductfromAnyStore` permission to ownership-aware checks mirroring `AssignProductToOwnStore` (Big3, owner, legacy manager, store_managers pivot member, or descendant of any of those).
- **Payload Keys**:
- `product_hashkey`: Used in URL payloads to identify a specific product.
- `store_hashkey`: Used in URL payloads to identify the store context for a product.
## Ultimate Console
- **Component**: `resources/js/Pages/UltimateConsole.vue` - Powerful administrative interface for ULTIMATE accounts.
- **Roles**: Access restricted via `ultimate` middleware and `UserActions::UltimateConsole`.
- **Key Features**:
- Raw SQL Query execution (`UltimateQueryModal.vue`).
- System-wide maintenance mode toggle (Redis-backed).
- Global broadcast messaging (Redis-backed).
- Batch operations for users, transactions, and store management.
- Database clearing/flushing for specific tables.
- System statistics and artisan command execution.
- **Backend**: `app/Http/Controllers/Market/UltimateController.php`
- **Migrate Endpoint**: `POST /admin/ultimate/migrate` → `UltimateController@runMigrate`. Runs `php artisan migrate --force` (non-interactive, prod-safe). Auth: `auth` + `ultimate` middleware. Returns `{ success, output }`.
- **Flush Endpoint**: `POST /admin/ultimate/flush` → `UltimateController@flushData`. Body `{ target }`. Valid targets: `transactions`, `pos_sessions`, `stores` (truncates `str` + `prd_str` + `store_managers`), `products` (truncates `prd_items` + `prd_str`), `cooperatives` (truncates `cooperative_*` child tables + deletes `organizations` where `type='COOPERATIVE'`), `carts` (truncates `cart_items` + `carts`), `farmer_profiles`, `cache` (Redis flushDB). UI lives in the "Dangerous Zone" tab via `flushOptions` in `UltimateConsole.vue`.
- **Composable**: `resources/js/composables/useUltimate.js` - Contains methods like `getStats` (to retrieve system metrics), `toggleMaintenance`, `sendGlobalMessage`, and `runCommand` (for Artisan calls). Ensure methods are called exactly as defined (e.g., `getStats` instead of `fetchStats`).
## POS (Point of Sale)
- **Main Interface**: `resources/js/Pages/PosMain.vue`
- **Buttons**: In dark mode, quantity adjustment buttons (`btn-light`) and the delete button (`btn-soft-danger`) should use theme-aware glassmorphism or deep charcoal backgrounds to avoid a "stark white" or "quirky" appearance.
- **Security**: Raw queries are restricted to `SELECT`, `SHOW`, and `DESCRIBE` for data retrieval; others use standard DB statements. Artisan commands are whitelisted.
- **Dark Mode Overlays**: Global overlays such as the `.loading-overlay` in `application-layout.blade.php` must be dark-mode aware. Ensure `background: rgba(var(--bg-card-rgb), 0.7)` or themed variables are used instead of hardcoded white backgrounds (`rgba(255, 255, 255, 0.7)`).
- **Hardcoded Backgrounds**: Avoid using `bg-white` or `bg-light` classes directly in Vue templates for components that should persist in dark mode (like modals in `PosMain.vue`). Use themed CSS variables or `:global(.dark-mode)` overrides.
- **listProductsData store resolution**: Must use sequential `if (!$targetStore)` guards, NOT `if/elseif`. A stale `access_key` in localStorage (e.g., a revoked key or session-specific token) must not block the `store_hash`/`session_hash` fallbacks. Priority order: access_key → session_hash → store_hash. Bug pattern: using `elseif` causes all global products to be shown when access_key is present but unrecognized.
- **Session Composable**: `resources/js/composables/Market/usePosSession.js` - Isolates POS initialization, session loading, and transaction completion logic. Use this in `PosMain.vue` and related components to ensure stable terminal identity and prevent logic fragmentation.
- **Session Hashkey Integrity**: The backend `PosController.php` must NOT truncate hashkeys at 36 characters. The `hashkey` field is 300 characters and can contain long uniquely generated identifiers. Incorrect truncation leads to 404 "Session not found" errors.
- **Performance Optimization**: To reduce perceived latency in local and production environments, the POS API uses **Request Consolidation**. The `addItem` and `removeItem` endpoints return the full current session object (with transactions and products) instead of just the single modified record. This eliminates the need for the frontend to make a second `loadSession` call, halving the total network delay for cart actions. Additionally, the backend implements **High-Efficiency SQL Paths**:
- **Joined Lookups**: Product pricing and store overrides are fetched in a single combined query to reduce database round-trips.
- **Aggregate Operations**: Session totals are calculated using database-side `SUM()` aggregates rather than memory-heavy PHP collection mapping.
- **Lean Archiving**: The `PosSessionArchive` logic only captures raw session attributes (`getAttributes()`) rather than nested relationship trees, which significantly reduces JSON serialization overhead and database write bloat.
- **Column Projection**: Eager loads are restricted to only necessary columns (`select(['id', 'hashkey', ...])`) to minimize the JSON payload size returned in the response.
## User Notes
- **Composable**: `resources/js/composables/useUserNotes.js`
- **Controller**: `app/Http/Controllers/Pages/AccountSettingsPageController.php`
- **Mechanics**: Fetches from `/user/note/content`. Also updated in real-time via SSE in `useSessionGuard.js`.
- **Security**: Requires `web` and `auth` middleware to ensure session persistence and prevent 302 redirects to login on AJAX calls.
- **Handling**: Frontend should strictly ignore HTML responses (which indicate redirects) to avoid displaying login page source code in the notes modal.
## Database Backups
- **Table**: `db_backups`
- **Model**: `App\Models\DbBackup`
- **Key Fields**: `hashkey`, `file_content_hashkey`, `filename`, `size_in_bytes`.
- **Logic**: Backups are generated as `.sql` files, compressed to `.7z` Ultra, and then stored as base64 in the `file_content` table. The `db_backups` table tracks these records. Temporary `.sql` and `.7z` files are deleted from the filesystem immediately after being saved to the database to prevent persistence on disk.
- **Exclusion**: The `db_backups` table is excluded from the SQL dump to prevent recursive growth.
- **UI**: Managed in `resources/js/Pages/UltimateConsole.vue` under the "Backups" tab.
- **Controller**: `app/Http/Controllers/Market/UltimateController.php`
## POS History
- **Component**: `resources/js/Pages/PosHistory.vue` - Standalone page for viewing a store's POS transaction history.
- **Route**: `/pos-history--h:STORE_HASH` (Mapped in `VueRouteMap.php`).
- **Logic**: Uses `PosHistoryList` component and `posStore` for paginated data retrieval.
- **Navigation**: In `PosMain.vue`, clicking the "Today: X txns" count navigates to this page.
- **Permissions**: Restricted to `ult`, `super operator`, `operator`, `store owner`, and `store manager`.
## Cart Module
- **Table**: `carts`, `cart_items`
- **Models**: `App\Models\Market\Cart`, `App\Models\Market\CartItem`
- **Controller**: `App\Http\Controllers\Market\CartController`
- **Page**: `resources/js/Pages/CartProductMarket.vue`
- **Logic**: Users have a single active cart. Cart items are linked to products from `prd_items`.
- **API Routes**: `/cart/get`, `/cart/add`, `/cart/update`, `/cart/remove`, `/cart/clear`.
## Global System Settings
- **Model**: `App\Models\SystemSetting.php` - Stores key-value configurations with caching.
- **Page**: `resources/js/Pages/SystemSettings.vue` - Admin interface for managing branding and system flags.
- **Helper**: `App\Support\SystemSettingsHelper.php` - Facade-like access for Blade and Controllers.
- **Branding**: Includes `app_name`, `app_logo`, `app_description`, `footer_text`, and `primary_color`.
- **Dynamic Branding (Frontend)**:
- **Pattern**: Use `uiStore.appName` in Vue templates instead of hardcoded strings like "BukidBounty".
- **Components**: `Home.vue`, `Login.vue`, `AccountSettings.vue`, and `HomePublic.vue` use this pattern.
- **Tab Title**: Automatically managed via `uiStore.pageTitle`, which defaults to `appName`.
- **Inertia Shared Data**:
- **Controller**: `App\Http\Controllers\Support\Inertia.php` - Automatically includes `systemSettings` in every `Inertia::render` call.
- **Synchronization**: `app.js` populates the `uiStore` from `initialPage.props.systemSettings` immediately upon mounting. This eliminates the "flash" of default branding before the API background refresh completes.
## Common Modals & UI Components
- **BaseModal**: `resources/js/Components/Core/BaseModal.vue` - Standardized modal structure with header, body, and footer slots. Supports `v-model` for visibility.
- **ConfirmModal**: `resources/js/Components/Core/ConfirmModal.vue` - Specialized modal for action confirmations (Maintenance toggle, Backups, Flushes). Supports `danger`, `warning`, and `info` variants.
- **UltimateQueryModal**: `resources/js/Components/Ultimate/UltimateQueryModal.vue` - Advanced SQL console for ULTIMATE accounts, refactored to use `BaseModal` and theme-aware styling.
- **FileImage**: `resources/js/Components/Core/FileImage.vue` — emits a single `<img>` with `@error` fallback. The `:src` prop is forwarded as-is, so callers must pass a string URL. `photourl` from product endpoints may be `string | string[] | null`; passing `photourl[0]` when `photourl` is a string yields a 1-char URL that 404s. Use a safe accessor like `const firstPhoto = (v) => Array.isArray(v) ? (v[0] || '') : (v || '');` before binding.
- **FileList::resolvedUrl()**: `app/Models/FileList.php` (table `file_list`) returns `cdn_url` if non-empty, else `/RequestData/File/{hashkey}`. `str.photourl` is a JSON array of `file_list` hashkeys — raw hashkeys are NOT renderable URLs. `StoreController@viewStoreDetails` resolves them into `$store->resolved_photos` (array of usable URLs, cdn_url preferred); `ViewStoreMarket.vue` reads `store.resolved_photos` for its avatar/banner/photo-gallery modal.
- **CardSimple**: `resources/js/Components/Core/CardSimple.vue` — wrapper with `is-premium` defaulting to `true`, which adds a translucent background and a `translateY(-2px)` lift on hover. For non-interactive panels (page headers, search bars, info boxes), pass `:is-premium="false"` to disable the hover lift, otherwise the surrounding card jitters when the cursor moves over it.
## Cooperative Chapter Officer/Member Features
- **User types**: `COOP_OFFICER` ('coop officer'), `COOP_MEMBER` ('coop member') in `app/Enums/UserTypes.php`. Home fragments: `resources/js/Pages/Fragments/Home/HomeCoopOfficer.vue`, `HomeCoopMember.vue` (wired in `Home.vue`).
- **Controller**: `app/Http/Controllers/Support/ChapterController.php` — methods `getOrgChart`, `getOfficerScope`, `memberSearch`, `assignOfficer`, `createChapter`, `publicGetChapter`, `publicRegisterToChapter`.
- **Routes** (`routes/web.php`): authed `POST /Chapters/OrgChart`, `/Chapters/Officer/Scope`, `/Chapters/Members/Search`, `/Chapters/Officer/Assign`, `/Chapters/Create`; public `GET /api/public/chapter/{hkey}`, `POST /api/public/chapter/register`.
- **Composable**: `resources/js/composables/useChapters.js` — `fetchOrgChart`, `fetchOfficerScope`, `searchMembers`, `assignOfficer`, `createChapter`.
- **SPA pages** (VueRouteMap, module 'cooperatives'): `/chapter-org-chart`, `/coop-member-search`, `/create-coop-user`, `/assign-chapter-officer`, `/create-chapter`, `/register-chapter` (public; decodes base64 `payload` prop `{coop_hash,chapter_hash}`).
- **CHILD_LEVELS**: national→region→province→[city|municipal]→barangay. `chapters.level` enum has NO 'municipal' (peers in code only). Level checks use `in_array($level,['city','municipal'])`.
- **assignOfficer** MOVES a member (deactivate parent ChapterMember, create child officer row, upgrade COOP_MEMBER→COOP_OFFICER). **memberSearch** returns ONLY {name, role, chapter_name}. HomeCoopMember shows officer names + member count only (never fellow member names).
- **home-data**: COOP_OFFICER/COOP_MEMBER branches set `$props['props']['chapter_info']` + stats `chapter_member_count`/`child_chapter_count`/`new_members_7d`.
- **Duplicate-check endpoints**: authed `POST /admin/user/number/exists` (`mobile_number`), `POST /admin/user/username/exists` (`username`) → `{exists}`; public `POST /api/public/user/check-mobile`.
## Account Type Definitions (RBAC)
- **ULTIMATE**: full system access, bypasses all hierarchy checks.
- **SUPER_OPERATOR**: management of all entities except ULTIMATE accounts. Includes global store and POS key management.
- **OPERATOR**: management of multiple stores, farmers, and logistics (Shipments/POS Reports). Now includes global store and product management, and POS access keys.
- **COORDINATOR**: focused on logistics, organizations, and farm management.
- **RIDER**: delivery and shipment status updates only; blocked from user management.
- **POS_TERMINAL**: point-of-sale operations, viewing POS reports, and customer lists.
- **AUDIT**: read-only view of all system data (Global Reports, Transactions, Accounting, User Lists).
- **ANY_USER**: Standard system user with no administrative or logistics privileges.
## Page Access Controls
- **Setting Key**: `disabled_pages` (stored as JSON in `system_settings`).
- **Logic**: Enforced at both the backend (`VueRouteMap.php`) and the frontend navigation layer (`useNavigate.js`).
- **Management**: Controlled via the "Page Controls" tab in `UltimateConsole.vue`.
- **Backend API**: `SystemSettingsController@getPublicSettings` exposes the list to the frontend, and `SystemSettingsController@update` handles saving toggled states.
- **Sync Mechanism**:
- **Initial Load**: `app.js` calls `uiStore.refreshSettings()` on startup.
- **Background Refresh**: `app.js` runs a background interval calling `uiStore.refreshSettings()` every 1 minute to stay in sync.
- **Pre-navigation Refresh**: `useNavigate.js` automatically triggers a refresh if settings are "stale" (older than 5 minutes) before evaluating the access guard. **Known Issue**: This threshold may cause a delay when a page is enabled/disabled until a reload or the 5-minute window expires.
- **Sync Strategy**: Admin tools (e.g., `UltimateConsole.vue`) should manually trigger `uiStore.refreshSettings()` or `syncSettings()` after updating `disabled_pages` to ensure immediate session-wide consistency.
- **Case-Insensitivity**: All disabled page checks are case-insensitive to ensure reliable blocking regardless of component naming conventions.
- **Affected Files**:
- `resources/js/composables/Core/useNavigate.js` - Global navigation guard.
- `resources/js/stores/ui.js` - Reactive store for `disabledPages` and `lastSynced` timestamp.
- `app/Http/Controllers/Support/VueRouteMap.php` - Backend enforcement for direct URL access; implements `isUserAllowed` logic using `allowedUserTypes` key.
- `app/Http/Controllers/Helpers/Permissions/UserPermissions.php` - Core RBAC engine mapping roles to `UserActions` enums.
- `app/Http/Controllers/Admin/SystemSettingsController.php` - API bridge for public setting retrieval.
## System vs Account Settings
- **Account Settings**: `AccountSettings.vue` - User-specific profile and security settings (password, photo, basic info).
- **Management**: Controlled via the "Page Controls" tab in `UltimateConsole.vue`.
- **Global System Settings**: `SystemSettings.vue` - Administrative configuration for the entire platform (branding, maintenance, page controls).
- **Navigation**: In `HomeUltimate.vue` and other navigation menus, "Global System Settings" must point to the `SystemSettings` pagename, while "My Personal Profile" or "Account" points to `AccountSettings`.
## User Additional Details
- **Settings Field**: JSON column in `users` table.
- **Cooperatives**: Stored in `settings.cooperatives` as an array of organization hashkeys.
- **Composable**: `resources/js/composables/useUserAdditionalDetails.js`
- **Controller**: `app/Http/Controllers/UserManagement/UserAdditionalDetailsController.php`
- **Functionality**: Allows users to join/leave cooperatives via settings, and provides quick search by cooperative membership.
- **Sync**: Automatically synced in `CooperativeController::joinCooperative` to maintain consistency between `cooperative_members` table and user settings.
## SSE & Real-time Integration
- **Controller**: `app/Http/Controllers/Support/SSEController.php`
- **Mechanism**: Uses `Hypervel\Coroutine\Parallel` for concurrent fetching of multiple data streams (Stats, Customers, Inventory, Settings) to minimize latency.
- **Pulse Interval**: 5 seconds.
- **Syncing Logic**:
- **Customers**: Employs a **First-Fetch vs Delta** strategy. Upon initial connection (or first fetch), the top 20 most recent customers are pushed to the client to "preload" search suggestions. Subsequent pulses only push records with `updated_at > lastSync` (deltas).
- **Products (Marketplace)**: Precaches the full active product list for the global marketplace on the **first fetch** (`isFirstFetch`). Subsequent ticks send detailed product updates (price, stock, descriptions) as `inventory_deltas`.
- **Inventory**: Product inventory updates (stock/price changes) are pushed as deltas based on the `lastSync` timestamp across all stores owned/managed by the user.
- **Frontend Integration**:
- **Composable**: `resources/js/composables/Core/useSessionGuard.js` acts as the primary consumer and dispatches data to Pinia stores.
- **Stores**: `pos.js` (`syncFromSSE`), `product.js` (`syncFromSSE`), and `ui.js` (`syncDisabledPages`) handle the reactive merge of incoming data into the UI state.
- **Caching**: The `product.js` store maintains a `detailsCache` (Object/Map) of full product details to enable instant page transitions in the Marketplace and Product Detail views.
## Session Management & PWA
- **Multi-tab Logout**: Synchronized via both SSE (`isloggedin: false` payload) and a `localStorage` `storage` event (`logout_event` key). When any tab logs out, it clears `localStorage` and sets `logout_event`, which triggers an immediate redirect to `/login` in all other open tabs via the `useSessionGuard` composable.
- **SSE Connection**: Handled in `resources/js/composables/Core/useSessionGuard.js`. It maintains a persistent `EventSource` connection to `/sse/stream` for real-time updates and session monitoring. The backend `SSEController` explicitly sends an `isloggedin: false` message before closing the stream when a session is lost.
- **Service Worker Registration**: Managed in `resources/js/app.js`. To ensure persistent SSE and background features, the service worker should NOT be unregistered upon landing on the login page; automatic unregistration logic in `Login.vue` was removed to prevent "service worker deleted" issues.
- **Polling Fallback**: If SSE is unavailable, the system automatically falls back to a Web Worker (`session-guard-worker.js`) or main-thread interval polling to ensure session integrity.
- **Observability**: Console logs with the prefix `[SessionGuard]` provide observability into connection status, version changes, and logout events.
- **Session Locking (SSE)**: Long-running SSE stream loops in PHP can lock the session associated with the request by default. This prevents concurrent requests from the same session (like logout, login, or any other middleware-protected route) from processing until the SSE thread yields. To avoid this "blocking" behavior, call `session()->save()` at the start of the loop in `SSEController` to release the lock immediately.
## User Management
- **Create User**: `resources/js/Pages/CreateUser.vue` - Main interface for creating new users.
- **Controller**: `app/Http/Controllers/UserManagement/CreateUserControllerUltimate.php`
- **Required Fields**:
- `mobile_number`: Unique, format `09XXXXXXXXX`.
- `username`: Unique, required for all users.
- `name`: Required (Display Name).
- `password`: Min 6 characters.
- `type`: User Type (via `UserTypes` enum).
- **Validation**: When the user clicks "Create User Account", a modal appears if any required fields are missing or invalid, listing them clearly with badges and a description of the requirements. This replaces the previous inline badge system.
- **Hierarchy**: Users can be assigned a `parentuid` (parent) to create a multi-level marketing or organizational hierarchy. The `listAllUsersforParentSelectHTML` method in the controller filters the parent list to only show the current user and their descendants, ensuring data isolation.
## Build & Deployment Standards
- **Requirement**: After any changes to the frontend (Vue components, assets) or backend logic, run the build and restart process to ensure the latest changes are active.
- **Build Process**:
- Primary: `npm run build` (on host, if node/npm are installed).
- Containerized Fallback: If `npm` is missing on the host, use a specialized Node container to build directly into the host filesystem:
`docker run --rm -v "$(pwd)":/var/app -w /var/app node:latest /bin/sh -c "npm install && npm run build"`
- **Vite Version**: The project uses **Vite 7**, which requires **Node.js >= 22.12.0** or **>= 20.19.0**.
- **Container Restart**: After building assets, restart the main application container to refresh the Swoole service and pick up the new build:
`docker restart bukidbountyapp`
- **Volume Mapping**: The application container mounts the current directory to `/var/app`, so build assets generated on the host are immediately visible to the container upon restart.
- **Branch Synchronization**: The `main` branch is periodically updated to match the `experimental` branch to promote stable experimental features to production. This is done via `git checkout main && git reset --hard experimental`.
## RBAC & Session Hardening Best Practices
- **User Type Listing (Dropdown Fix)**: The `CreateUserControllerUltimate@listAllUserTypesforSelectHTML` method must handle `UserTypes` enums directly. Avoid using `UserTypes::from($currentUser->acct_type)` if the `acct_type` attribute is already cast to the enum in the `User` model, as this will trigger a `TypeError`.
- **SPA Route Protection**: All sensitive management pages (e.g., `/create-user`) **MUST** be explicitly defined in `App\Http\Controllers\Support\VueRouteMap::$routes` with an `allowedUserTypes` array. If a route is not mapped, it defaults to allowing any authenticated user via the `handleSpa` catch-all, which creates a security vulnerability.
- **Session State Isolation**: To prevent "Role Leaking" (where a new user sees the previous user's dashboard fragments or cached roles), the `Login.vue` component must execute `sessionStorage.clear()` within its `onMounted` hook. This ensures a clean slate for every new authentication attempt.
- **Dashboard Fragmentation**: Dashboard fragments in `resources/js/Pages/Fragments/Home/` (e.g., `HomeShared.vue`, `HomeOperator.vue`) should implement `computed` filtering for action buttons based on `UserTypes` to ensure that unauthorized actions are not visible in the UI, even if the user has navigated to the dashboard correctly.
## Expanded Member Profile (Cooperative)
- **Table**: `user_infos`
- **Personal Details**: `firstname`, `middlename`, `lastname`, `suffix`, `gender`, `dob`.
- **Dynamic Context**: `priority_sector` (links to `priority_sectors` in system settings).
- **Social Accounts**: `messenger_id`, `viber_number`, `tiktok_username`.
- **Address Components**: `region`, `province`, `city`, `barangay`. Detailed address (House No, Street, Zip, Country) is stored in the `addresses` JSON column.
- **Family & Education**: `civil_status`, `children_count`, `dependent_count`, `education_level`, `course`, `school`, `year_last_attended`.
- **Employment & Livelihood**: `livelihood_source`, `last_company`, `employer_name`, `last_position`, `occupation`, `last_employment_year`.
- **Financial Details**: `monthly_income` (decimal), `bank_account_no` (string).
- **Government Info**: `tin`, `philhealth_id`, `gov_id`, `id_type`, `id_number`, `beneficiary_type`.
- **Emergency Contact**: `emergency_contact_name`, `emergency_contact_address`, `emergency_contact_phone`, `emergency_contact_relation`, `emergency_contact_user_id`.
- **Linking Logic**: The system automatically links emergency contacts by matching their phone number with an existing user's `mobile_number` in `UserInfoController@updateUserInfo`.
- **Virtual Attributes**: `age` (calculated on the fly from `dob`).
## Public Cooperative Registration
- **Route (Frontend)**: `/register-coop--h:HASHKEY` → `RegisterCoop.vue` (`loginRequired: false`).
- **Public API**: `GET /api/public/cooperative/{hkey}` returns coop info (no auth). `POST /api/public/cooperative/register` creates a new USER account and registers them as a MEMBER.
- **Controller Methods**: `CooperativeController@publicGetCooperative` and `CooperativeController@publicRegisterMember`.
- **Parent Assignment**: Uses the cooperative's `created_by` user as parent, falling back to first COORDINATOR, then first user.
- **Share Button**: In `CooperativeDetail.vue` → "Share Register Link" button uses the Web Share API on mobile (native dialog) and falls back to clipboard copy on desktop.
## Cooperative Membership Details
- **Table**: `cooperative_members`
- **Key Fields**: `membership_type`, `membership_level`, `officer_position`, `officer_level`, `concurrent_position`, `concurrent_level`, `cooperative_name_alt`, `cooperative_position`, `year_beginning`.
- **Management**: Membership details can be updated via `/Cooperatives/Member/Update`.
- **Self-Service Registration**: Users can register themselves as members via the `CooperativeMemberRegister.vue` page (route: `/cooperative-member-register--h:COOP_HASH`).
- **Registration Logic**: Handled by `CooperativeController@registerMember`. It automatically links the user to the organization and updates the `settings.cooperatives` array in the `users` table for session/preference synchronization.
- **Access Control**: Controlled by `UserActions::JoinCooperative`. Standard users, operators, and coordinators have this permission by default.
- **UI Integration**: A "Register as Member" button appears on the `CooperativeDetail.vue` page if the user is not yet a member. After registration, the button is hidden and the user appears in the members list.
## RBAC & Hierarchy Concepts
- **Big 3**: Refers to `ULTIMATE`, `SUPER_OPERATOR`, and `OPERATOR` account types. They have global management privileges, bypass standard organizational hierarchy checks, and are the only roles permitted to edit global product details (categories, base prices, etc.) in `ManageProductAdmin.vue`.
- **Hierarchy Management**: Management actions (Listing, Viewing, Editing) for stores and users are typically restricted to direct or indirect parents (ancestors). "Big 3" roles are immune to this restriction when accessing stores.
- **Store Managers**: Users assigned via the `store_managers` table who are granted delegated management access to a specific store.
- **Multi-Manager System**: Replaces the legacy single-manager field (`manager_id`) with a searchable, multi-select interface in `CreateStore.vue` and `EditStoreUltimate.vue`, allowing multiple users to be assigned as managers for a single store.
- **Context-Aware Product Locking**: A UI pattern in `ManageProductAdmin.vue` that hides or disables global product fields for non-"Big 3" users, while still allowing them to manage store-specific overrides if they are an owner or manager.
## POS Access Control (Hardened)
- **Helper**: `UserPermissions::isUserAllowedAccessToStore($user, $store)` - Centralized authorization check for POS/Reports access.
- **Logic**:
- `ULTIMATE` users have global access.
- Owners and Managers of the store have access.
- Any ancestor of the Owner or Manager has access.
- Children (e.g., `POS_TERMINAL`, `RIDER`) of the Owner or Manager have access.
- **Enforcement**: Applied to `PosController` methods: `startSession`, `getSession`, `getPosSessions`, `getTodayStats`, and `getCustomers`.
- **Ancillary Helper**: `UserPermissions::isAncestorOf($ancestor, $descendant)` - Recursively checks user hierarchy via `parentuid`.
## UI Audit Results (April 2026)
- **Navigation**: "My Personal Profile" now explicitly passes the current user's hashkey in HomeUltimate.vue.
- **Connectivity**: SSE connection refused (ERR_CONNECTION_REFUSED) is a known issue being investigated at the container/proxy level.
- **Functional Checks**:
- **UserInfoEdit**: Fixed hanging by ensuring loader always closes and defaults to current user.
- **Create/Manage Store**: Confirmed functional with correct route mapping.
- **Ultimate Console**: Operational with full monitoring and setting controls.
## Remote Development & Tunneling
- **Script**: `scripts/tunnel.sh` - Standardized script for creating a Cloudflare Tunnel to `localhost:9522`.
- **Port**: `9522` (Host) maps to `9501` (Container) for the `bukidapp` service.
- **Usage**:
- Temporary URL: `./scripts/tunnel.sh` (generates a `*.trycloudflare.com` link).
- Persistent Tunnel: `./scripts/tunnel.sh <YOUR_TUNNEL_TOKEN>`.
- **Implementation**: Uses Docker `cloudflare/cloudflared` with `--network host` to expose the local development port online for testing and external access.
## Dokploy Deployment
- **Guide**: `dokploy-deployment-guide.md` (repo root) — Full step-by-step deployment guide.
- **Network**: All services (app, MySQL, DragonflyDB) must join `dokploy-network` (external: true) for DNS resolution.
- **Hostname Resolution**:
- **Swarm services** (Dokploy-managed databases like MySQL): Use the **service name** as hostname (e.g., `prodservers-prodsqlmain-kv3yph`). Find via `docker service ls`.
- **Compose services** (like DragonflyDB): Use the **full container name** as hostname (e.g., `prodservers-dragonflydb-rnfaje-dragonflydb-1`). Find via `docker ps --format '{{.Names}}'`.
- **Common Error — Redis DNS Lookup Failed**: Caused by `REDIS_HOST` pointing to a container name that is not on the same Docker network. Fix by adding `dokploy-network` to the DragonflyDB compose and using the correct container name.
- **Port Exposure**: Do NOT use host port mapping (e.g., `9522:9501`) in `docker-compose.yml` for Dokploy. Expose only the internal port (`9501`); Traefik handles external routing via auto-injected labels.
- **Variants**: Multiple app instances (prod, beta, test) can share the same MySQL server and DragonflyDB by using different `DB_DATABASE` names and `REDIS_DB` numbers (015).
## Landing Page Management
- **Table**: `landing_pages`
- **Model**: `App\Models\LandingPage`
- **Key Fields**: `title`, `html_content`, `description`, `hashkey`, `is_active`.
- **Active Constraint**: Only one landing page can be `is_active=true` at a time, enforced at the application level via `LandingPage::setAsActive()`.
- **Controller**: `App\Http\Controllers\Admin\LandingPageController`
- **Editor Page**: `resources/js/Pages/LandingPageEditor.vue` — Full CRUD interface with HTML editor, live preview, template snippets (hero/features/CTA), and card-based gallery view.
- **Guest Rendering**: `HomePublic.vue` fetches the active landing page via `/api/public/landing-page` and renders the HTML for unauthenticated visitors. Falls back to the default homepage if no active landing page exists.
- **Access Control**: Landing page management is restricted to `ULTIMATE`, `SUPER_OPERATOR`, and `COORDINATOR` roles.
- **RBAC**: `UserActions::ManageLandingPages` permission added to `$RoleswithNoTargetUser`.
- **Route Map**: Registered as `LandingPageEditor` in `VueRouteMap.php`.
- **Public API**: `GET /api/public/landing-page` (no auth) returns the active landing page HTML content.
- **Admin APIs**:
- `POST /admin/landing-pages/list` — List all landing pages.
- `POST /admin/landing-pages/show` — Get a single landing page by hashkey.
- `POST /admin/landing-pages/store` — Create or update (if hashkey provided).
- `POST /admin/landing-pages/set-active` — Set a page as active (deactivates all others).
- `POST /admin/landing-pages/deactivate-all` — Deactivate all pages (revert to default homepage).
- `POST /admin/landing-pages/delete` — Delete a landing page.
## CDN Asset Pipeline (jsDelivr / obj-vault-3a)
- **CDN Repo**: `telemagnadon/obj-vault-3a` on GitHub, served via jsDelivr.
- **Pinned Tag**: Configured in `config/cdn.php` (`base`) and `public/sw.js` (`CDN_SHA`). Current: `v2026.05.14-vendor` (SHA `927de981da85`).
- **Asset Path Convention**: `a/<sha256-12char>.<ext>`. Extension is `.bin` by default to obscure content type. Whitelist that **preserves original extension**: `.css`, `.js`, `.mjs`, `.json`, `.svg`.
- **SVG Critical**: jsDelivr serves all files with `Content-Type: application/octet-stream` + `X-Content-Type-Options: nosniff`. Browsers will render PNG/JPG inside `<img>` from octet-stream (image decoder sniffs bytes anyway), but **SVG requires `image/svg+xml`** — `<img src="*.bin">` containing SVG markup silently fails to render. Therefore SVGs MUST be stored as `a/<hash>.svg` so jsDelivr serves them with the correct MIME.
- **Pipeline Skill**: `cdn-asset-pipeline` (in `~/.claude/skills/`) handles sync. After updating extension whitelist, all SVG `.bin` files must be re-synced to `.svg` and orphaned `.bin` files removed.
- **CSS Relative URLs Break on CDN**: CSS files served from CDN use the flat `a/<hash>.<ext>` path, so any relative `url(...)` inside them (e.g. `@font-face src: url('icomoon.ttf')`) resolves to `a/icomoon.ttf` and 404s. **Fix**: rewrite any internal asset references in CSS to absolute jsDelivr URLs pointing at their published hashed `.bin`/`.svg`. Known offender: `public/assets/vendor/dist/alt-theme/icons-alipay.css` (icomoon @font-face). When breaking this, top-bar back/filter icons and bottom-nav home icon (any `.icon-*` class from icomoon) silently render blank.
- **Icomoon Font Path**: `public/assets/vendor/dist/alt-theme/icomoon.ttf` → CDN `a/c99ad0580805.bin`. Reference absolutely in `icons-alipay.css` so font loads regardless of where the CSS is hosted.
- **Icomoon Font Broken on jsDelivr (May 2026)**: The `.ttf` is published as `a/c99ad0580805.bin` and served as `application/octet-stream` with `X-Content-Type-Options: nosniff`. Chrome/Firefox refuse to use it as a font, silently breaking every `.icon-*` class. Same MIME-sniff problem the SVG migration solved. **Resolution**: don't use `icomoon` `.icon-*` classes in Vue components — use Font Awesome (`fas fa-*`, already loaded). Mappings used: `icon-home → fa-home`, `icon-left → fa-chevron-left`, `icon-right → fa-chevron-right`, `icon-copy2 → fa-copy`, `icon-filter → fa-sliders-h`. If icomoon is ever needed again, either add `.ttf/.woff/.woff2/.eot/.otf` to the CDN pipeline's preserve-extension whitelist and re-publish, or self-host the font under `/public` so Nginx sets the correct MIME.
## Service Worker & Caching
- **Hashed Assets**: Files under `/build/assets/` (generated by Vite) include hashes in their names.
- **Caching Strategy**: These files use a **Cache-First** strategy since their content is immutable for a given filename.
- **Expiration**: Cached for 6 months (`180 * 24 * 60 * 60` seconds) with a maximum of 1000 entries.
- **Implementation**: Configured in `public/sw.js` via Workbox `registerRoute`.
## Infrastructure & Docker
- **Main Service**: `bukidapp` - PHP Swoole/Hypervel application service.
- **Container Name**: Usually `bukidbountyapp-bukidapp-1` (or `bukidbountyapp-nginx-1` for the entry point).
- **Port**: Internal `9501`, exposed to the host/nginx.
- **Reverse Proxy**: Nginx acts as the entry point and reverse proxy, configured in `docker/nginx/default.conf`.
- **Static Asset Delivery**: Nginx serves files from `/public` directly (CSS, JS, images) to minimize latency and bypass the PHP/Swoole stack for static requests.
- **Docker Compose**: Managed via `docker-compose-local.yml` (development) and `docker-compose.yml` (production).
- **Service Dependency**: The `nginx` service depends on `bukidapp`.
- **Networking**:
- Uses `dokploy-network` (external) for Traefik routing and cross-service communication.
- Uses `app-internal` (bridge) as an **isolated network** for nginx→bukidapp communication to avoid DNS collisions.
- **DNS Collision Warning**: On `dokploy-network`, multiple compose projects may share the same service name `bukidapp`. Nginx must NOT resolve `bukidapp` directly on this shared network. Instead, use a unique network alias (`swoole-upstream`) on the `app-internal` network.
- **Upstream Alias**: The `bukidapp` service declares `aliases: [swoole-upstream]` on `app-internal`, and `docker/nginx/default.conf` targets `server swoole-upstream:9501`.
- **Priority Sectors**: Stored as a JSON array in `system_settings` under the key `priority_sectors`. Defines groups eligible for specific assistance and priority (e.g., FISHERMEN, SENIOR, PWD). Managed via the Global System Settings editor.
- **Group Types**: Stored as a JSON array in `system_settings` under the key `group_types`. Defines the categories of organizations (e.g., COOPERATIVE, ASSOCIATION).
- **Address Fields**: Stored as a JSON array in `system_settings` under the key `default_address_fields`. Defines the expected keys in the `user_infos.addresses` JSON (e.g., `house_no`, `street`, `zipcode`).
- **JSON Setting Editor**: A dynamic editor in `SystemSettings.vue` that supports array-based configurations with tag/badge management.
- **Dokploy Traefik Routing**:
- Dokploy auto-injects Traefik labels on the first service by default.
- **Domain Target**: In Dokploy UI → Domains tab, the service name MUST be set to `nginx` (not `bukidapp`), so Traefik routes `domain → nginx:80 → bukidapp:9501`.
- Traefik uses Docker labels (not DNS) to find the target container, so the nginx service MUST be on `dokploy-network` for Traefik to reach it.
- **Local vs Production Volumes**:
- **Local (`docker-compose-local.yml`)**: Uses bind mount `.:/var/app` for live code reloading during development. Nginx mounts `./public:/var/app/public` directly.
- **Production (`docker-compose.yml`)**: Does **NOT** use bind mounts on `bukidapp`. The image is self-contained — `composer install` and `npm install` run during `docker build`, and the resulting `vendor/` and `node_modules/` are baked into the image.
- **Shared Build Volume (Production)**: A named volume `public_build` shares Vite-built assets from bukidapp to nginx. bukidapp copies `/var/app/public/` to `/var/app/public-shared/` (the shared volume) on startup via the command `sh -c "cp -r /var/app/public/. /var/app/public-shared/ && exec php artisan serve"`. Nginx mounts this volume at `/var/app/public`.
- **Why not bind mount in prod?**: The host's `./public` directory (from the repo clone) does NOT contain Vite build output — those are generated inside the bukidapp container during `docker build`. A bind mount would hide the built assets.
- **Dockerignore**: `.dockerignore` excludes `vendor/`, `node_modules/`, `.git`, and `.env*` to prevent host directories from overwriting dependencies installed during image build via `COPY . .`.
## Module Enable/Disable System
- **Purpose**: Allows enabling or disabling entire application modules (POS, Products, Accounting, etc.) via environment variables without code changes.
- **Config**: `config/modules.php` - Central registry of modules and their associated `MODULE_<NAME>_ENABLED` environment variables.
- **Master Toggle**: `MODULES_SYSTEM_ENABLED` - If set to `false`, all module-specific checks are bypassed (everything is enabled). Defaults to `true`.
- **Helper**: `App\Support\ModuleHelper::isEnabled($moduleKey)` - Used to check module status in code.
- **Middleware**: `module:<key>` (e.g., `module:pos`) - Applied to routes in `web.php` to block API/web access to disabled modules with a 403 response.
- **SPA Routing**: `VueRouteMap.php` - Each SPA route can be mapped to a module using the `'module'` key. The router (`handleSpa` and `registerRoutes`) automatically blocks access and redirects to `/` if the module is disabled.
- **Frontend Sync**: Module states are exposed via `SystemSettingsController@getPublicSettings` under the `module_states` key. This allows the frontend to reactively hide navigation links or UI elements for disabled modules.
## QRPH / EMV Payment QR
- **Standard**: QRPH is the Philippine QR payment standard based on EMVCo Merchant-Presented QR Code Specification (TLV encoding: 2-char tag + 2-char decimal length + value).
- **Decoder**: `app/Http/Controllers/Helpers/QrphDecoder.php` — parses raw QRPH strings into merchant name, network, account, validity (CRC-16/CCITT-FALSE), initiation method, currency.
- **Storage**: QRPH payment code is stored in `system_settings` under key `qrph_payment_code`.
- **Action**: `UserActions::ManageQrphPaymentCode` (`manageqrphpaymentcode`) — ULTIMATE-only via route middleware; listed in `$RoleswithNoTargetUser`.
- **Routes**:
- `POST /Financial/Qrph/Get` — any authenticated user; returns raw string + decoded info for top-up display.
- `POST /Financial/Qrph/Set` — `ultimate` middleware; saves QRPH code to system_settings.
- `POST /Financial/Qrph/Decode` — `ultimate` middleware; decode-only preview (no save).
- **JS Composable**: `resources/js/composables/useQrph.js` — `parseTlv`, `injectAmount` (inserts EMV field 54 + recalculates CRC-16/CCITT-FALSE), `generateQrDataUrl` (renders QR via `qrcode` npm library — no external API).
- **Amount injection**: In `MyWallet.vue` QRPH tab, selecting an amount rebuilds the QRPH string client-side with the amount embedded, then renders a new QR — member's banking app (GoTyme, GCash, Maya, etc.) auto-fills the amount.
- **GoTyme / InstaPay GUIDs**: `QrphDecoder.php` recognises `com.gotyme`, `ph.ppmi`, `ph.bsp.qrph`, `com.p2pqrph` (and all major PH banks). Raw GUID is shown as fallback for unrecognised networks.
- **Top-Up UI**: `MyWallet.vue` top-up modal "Scan to Pay" tab: generates QR client-side with optional amount embedded; falls back to stored static image if no amount set.
- **Admin UI**: `AccountSettings.vue` shows a "Payment QR Code (QRPH)" card only for ULTIMATE accounts; drop-zone decodes QR image locally via `Html5Qrcode.scanFile`, uploads the image file, and shows Static/Dynamic badge.
## Known Bug Patterns & Fixes
### RBAC — Action Permission vs Page Access Mismatch
- VueRouteMap `allowedUserTypes` controls SPA page access; `UserPermissions::roles()` controls individual API action access. These must stay in sync.
- Example: `OPERATOR` listed in `/list-properties` `allowedUserTypes`, but `ViewProperties`/`ViewReferrals` were missing from `UserTypes::OPERATOR->value` in `roles()` → caused 403 on the `POST /admin/properties/list` API.
- Fix: always add the required `UserActions::*` to `roles()` whenever a role is added to `allowedUserTypes` for a page.
### SPA Catch-all — Undefined $moduleKey
- `VueRouteMap::handleSpa()` uses `$moduleKey` on line ~530 but only assigns it inside the `if/else` match blocks. If the path matches no known route, `$moduleKey` is undefined → potential 500.
- Fix: initialize `$moduleKey = null;` before the `if ($routeSettings)` block.
- `/bukidbountyapp` path is now registered in VueRouteMap as an alias of `Home` (no login required) to prevent it hitting the catch-all fallback.
### useUserNotes — Unauthenticated fetch
- `TopHeader.vue` calls `fetchNotes()` in `onMounted` unconditionally, firing `GET /user/note/content` (an auth-protected endpoint) before the user logs in.
- Fix: guard the call with `if (userStore.isLoggedIn)` in TopHeader, and add the same guard inside `useUserNotes::fetchNotes()` itself as a safety net.
### POS Access Key — STORE_OWNER / STORE_MANAGER missing create/delete/toggle permissions
- `/pos-access-keys` VueRouteMap allows `store owner` and `store manager`, but `roles()` only gave them `ViewPosAccessKeys`. Calls to create/delete/toggle keys returned 403.
- Fix: add `CreatePosAccessKey`, `DeletePosAccessKey`, `TogglePosAccessKey` to both `STORE_OWNER` and `STORE_MANAGER` in `UserPermissions::roles()`.
- **Default State**: All modules default to `true` (enabled) if the environment variable is missing.
## Database Table Names (canonical)
**Always reference these exact table names** in raw SQL, `unique:`/`exists:` validation rules, `DB::table(...)`, joins, and migrations. Several tables use abbreviated names — using the human/plural form (e.g. `stores`, `products`) causes `Table not found` SQL errors that Hypervel surfaces as a generic 400 with empty body.
### Market / Commerce
- `str` — **Stores** (model: `App\Models\Market\Store`)
- `prd_items` — **Products** (model: `App\Models\Market\Product`)
- `prd_str` — **Product↔Store pivot** (per-store price, stock, description, is_active)
- `prd_trx` — **Product Transactions** (model: `ProductTransaction`). Fields used by sales aggregation: `product_id`, `store_id`, `quantity`, `created_at`. `ProductController::viewProductDetails` aggregates `sold_today` (global) and `store_sold_today` (per-store, when a store hash is provided) by summing `quantity` filtered with `whereDate('created_at', today())`. Surfaced in `BuyViewProductMarket.vue` POS scan card alongside a `printPosCode()` button (opens a small window with QR + barcode and auto-`window.print()`/close).
- `prd_trx_ses` — **Product Transaction Sessions** (model: `ProductTransactionSession`)
- `prd_trx_ses_arc` — **Product Transaction Sessions Archive** (model: `ProductTransactionSessionArchive`)
- `cst` — **Customers** (model: `App\Models\Market\Customer`)
- `carts` — Carts
- `cart_items` — Cart Items
- `store_managers` — Store ↔ Manager User pivot (multi-manager system)
- `couriers` — Couriers
- `shipments` — Shipments
- `pos_access_keys` — POS Access Keys
- `pos_sessions` — POS Sessions
- `pos_sessions_archive` — Archived POS Sessions
- `pos_transactions` — POS Transactions
### Organizations / Cooperatives
- `organizations` — Organizations (cooperatives, associations, etc.)
- `main_organizations` — Top-level / parent organization records
- `org_str` — **Organization↔Store pivot** (cooperative-store linkage)
- `cooperative_members` — Cooperative Members
- `cooperative_documents` — Cooperative Documents
- `cooperative_resolutions` — Cooperative Resolutions
- `cooperative_votes` — Cooperative Votes
- `chapters` — Chapters
- `chapter_members` — Chapter Members
- `groups` — Groups
- `group_members` — Group Members
- `farmer_profiles` — Farmer Profiles
### Users / Auth / Accounting
- `users` — Users
- `user_infos` — User Info (addresses JSON, profile extras)
- `personal_access_tokens` — API personal access tokens
- `accounts` — Accounting accounts
- `account_transactions` — Account Transactions
- `member_ledgers` — Member Ledgers
- `global_transactions` — Global Transactions (cross-domain ledger)
### Properties / Referrals
- `properties` — Properties
- `referrals` — Referrals
- `referral_keys` — Referral Keys
### Files / System
- `file_list` — File List (uploaded file index)
- `file_content` — File Content (binary/blob storage)
- `landing_pages` — Landing Pages
- `announcements` — Announcements
- `system_settings` — System Settings (key/value, JSON values supported)
- `db_backups` — Database Backup records
- `logs` — Generic Logs
- `table_logs` — Per-table audit logs (model: `App\Models\Generic\TableLog`)
## Performance / Load-Testing API
- **Purpose**: Token-gated REST endpoints for hammering the box from a client machine over curl — exercise creation paths (users/stores/products) and the POS hot path while capturing per-op timing in milliseconds.
- **Controller**: `app/Http/Controllers/Market/PerformanceController.php` (acts as an `ULTIMATE` system user; pick a specific actor via env `PERF_ACTOR_HASH`).
- **Routes** (declared in `routes/api.php`, true API routes — NOT in `web.php`):
- `GET /api/perf/ping` — token check / health.
- `POST /api/perf/seed/users` — body `{count, type?, parent_hash?, prefix?}`.
- `POST /api/perf/seed/stores` — body `{count, owner_hash?, category?, prefix?}`.
- `POST /api/perf/seed/products` — body `{count, target_store_hash?, attach_to_store?, prefix?}`.
- `POST /api/perf/seed/batch` — body `{users?, stores?, products?, type?, category?, prefix?}` runs all three, returns per-phase ms.
- `POST /api/perf/pos/simulate` — body `{store_hash, items?, cycles?, complete?}` runs N end-to-end POS cycles (open session → bulk insert N line items via raw `pos_transactions` insert → optional complete). Reports `avg/min/max cycle_ms` plus per-cycle `open_ms`/`add_items_ms`/`complete_ms`.
- **Auth**: header `X-Perf-Token: <env PERF_API_TOKEN>` (also accepted as `?token=` query/body). If env unset, every endpoint 403s — feature is off by default.
- **Limits**: each seeder is clamped to 1..1000 per call; POS sim is clamped to ≤200 items per cycle and ≤100 cycles per call.
### Rule of thumb
- Plural English names (`users`, `accounts`, `shipments`, `organizations`, `properties`, `couriers`, `carts`, `groups`, `chapters`, `announcements`, `landing_pages`, `system_settings`, `personal_access_tokens`) → safe to assume the table name matches.
- **Abbreviated tables** (`str`, `cst`, `prd_items`, `prd_str`, `prd_trx`, `prd_trx_ses`, `prd_trx_ses_arc`, `org_str`) → **never guess** — use the canonical name from this list.
- When writing `unique:<table>,<col>` or `exists:<table>,<col>` rules, look up the model's `protected ?string $table` value rather than pluralizing the model class name.

View File

@@ -0,0 +1,517 @@
# Dokploy Deployment Guide — BukidBountyApp (Hypervel/Swoole)
> This guide documents the exact process for deploying BukidBountyApp (and derivative projects) on a Dokploy-managed server. It is based on real troubleshooting and verified configuration.
---
## Table of Contents
1. [Architecture Overview](#architecture-overview)
2. [Prerequisites on Dokploy Server](#prerequisites-on-dokploy-server)
3. [Step 1: Create Infrastructure Services](#step-1-create-infrastructure-services)
4. [Step 2: Deploy the Application](#step-2-deploy-the-application)
5. [Step 3: Configure Environment Variables](#step-3-configure-environment-variables)
6. [Step 4: Configure Domain & HTTPS](#step-4-configure-domain--https)
7. [Step 5: Create the Database](#step-5-create-the-database)
8. [Step 6: Run Migrations & Seed](#step-6-run-migrations--seed)
9. [Deploying Test/Beta Variants](#deploying-testbeta-variants)
10. [Troubleshooting](#troubleshooting)
11. [Reference: Working Configuration Files](#reference-working-configuration-files)
---
## Architecture Overview
```
Internet → Traefik (ports 80/443) → dokploy-network → bukidapp container (port 9501)
→ DragonflyDB (port 6379)
→ MySQL (port 3306)
```
| Component | Type | Network |
|-------------------|--------------------------|------------------|
| BukidBountyApp | Docker Compose (Dokploy) | `dokploy-network`|
| MySQL 8 | Dokploy Database (Swarm) | `dokploy-network`|
| DragonflyDB | Docker Compose (Dokploy) | `dokploy-network`|
| Traefik | Managed by Dokploy | `dokploy-network`|
---
## Prerequisites on Dokploy Server
- Dokploy installed and running
- Traefik configured with Let's Encrypt cert resolver
- DNS pointing your domain to the server IP
---
## Step 1: Create Infrastructure Services
### 1a. Create MySQL Database
1. In Dokploy UI → **Create****Database****MySQL**
2. Set root password, default user, and password
3. Dokploy will create a Swarm service (e.g., `prodservers-prodsqlmain-kv3yph`)
> **⚠️ IMPORTANT:**
> The `DB_HOST` for your app is the **Swarm service name**, not the container name.
> For Swarm services, use the service name directly (e.g., `prodservers-prodsqlmain-kv3yph`).
**Find the MySQL service name (run on Hostinger server terminal):**
```bash
docker service ls | grep -i sql
```
### 1b. Create DragonflyDB (Redis Alternative)
1. In Dokploy UI → **Create****Compose** → new project for DragonflyDB
2. Use this `docker-compose.yml`:
```yaml
version: '3.8'
services:
dragonflydb:
image: 'docker.dragonflydb.io/dragonflydb/dragonfly'
ulimits:
memlock: -1
ports:
- "6379:6379"
volumes:
- dragonflydata:/data
environment:
- DFLY_requirepass
networks:
- dokploy-network
volumes:
dragonflydata:
networks:
dokploy-network:
external: true
```
3. Deploy the service
> **⚠️ IMPORTANT:**
> The `REDIS_HOST` for your app is the **full container name**, not the Dokploy project name.
> For Docker Compose services, the container name follows the pattern: `{project-name}-{service-name}-{replica}`.
**Find the DragonflyDB container name (run on Hostinger server terminal):**
```bash
docker ps --format '{{.Names}}' | grep dragonfly
# Example output: prodservers-dragonflydb-rnfaje-dragonflydb-1
```
> **🔴 CRITICAL DISTINCTION:**
> - **Swarm services** (Dokploy Databases) → use the **service name** as hostname
> - **Compose services** (like DragonflyDB) → use the **container name** as hostname
>
> These are different! Always verify with `docker service ls` or `docker ps --format '{{.Names}}'`.
---
## Step 2: Deploy the Application
1. In Dokploy UI → **Create****Compose**
2. Connect to your Git repository (e.g., `git@git.cr8.space:josh/BukidBountyApp.git`)
3. Set branch to `main`
4. The `docker-compose.yml` in the repo will be used automatically
### Working `docker-compose.yml`
```yaml
services:
bukidapp:
build:
context: .
dockerfile: Dockerfile.php
ports:
- 9501
networks:
- dokploy-network
healthcheck:
test: [ "CMD-SHELL", "curl -f http://localhost:9501/health || exit 1" ]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
dokploy-network:
external: true
```
> **🔴 CAUTION:**
> Do **NOT** use host port mapping like `9522:9501`. Just expose the internal port `9501`.
> Traefik handles external routing via labels that Dokploy injects automatically.
---
## Step 3: Configure Environment Variables
In Dokploy UI → your service → **Environment** tab, set:
```env
# === Database (MySQL) ===
DB_CONNECTION=mysql
DB_HOST=<mysql-swarm-service-name>
DB_DATABASE=bukid
DB_PORT=3306
DB_USERNAME=mysql
DB_PASSWORD=<your-mysql-password>
# === Cache/Redis (DragonflyDB) ===
CACHE_DRIVER=redis
REDIS_HOST=<dragonflydb-container-name>
REDIS_AUTH=<your-dragonfly-password>
REDIS_PORT=6379
REDIS_DB=0
```
### How to Find the Correct Hostnames
| Service | Type | How to Find Hostname | Example |
|-------------|--------|------------------------------------------------------------|-------------------------------------------------------|
| MySQL | Swarm | `docker service ls \| grep sql` | `prodservers-prodsqlmain-kv3yph` |
| DragonflyDB | Compose| `docker ps --format '{{.Names}}' \| grep dragonfly` | `prodservers-dragonflydb-rnfaje-dragonflydb-1` |
| PostgreSQL | Swarm | `docker service ls \| grep pgsql` | `prodservers-prodpgsqlmain-x8frsw` |
> **💡 TIP:**
> You can verify connectivity from the app container using Dokploy's container terminal (UI):
> ```bash
> ping -c 2 <hostname>
> ```
---
## Step 4: Configure Domain & HTTPS
1. In Dokploy UI → your service → **Domains** tab
2. Add domain:
- **Host**: `bukid.hesed.sbs` (or your domain)
- **Container Port**: `9501`
- **HTTPS**: Enabled
- **Certificate**: Let's Encrypt
3. **Deploy/Redeploy** the service
### What Dokploy Adds Automatically
Dokploy injects these Traefik labels into your compose at deploy time:
```yaml
labels:
- traefik.docker.network=dokploy-network
- traefik.enable=true
- traefik.http.routers.<id>-web.rule=Host(`bukid.hesed.sbs`)
- traefik.http.routers.<id>-web.entrypoints=web
- traefik.http.services.<id>-web.loadbalancer.server.port=9501
- traefik.http.routers.<id>-web.middlewares=redirect-to-https@file
- traefik.http.routers.<id>-websecure.rule=Host(`bukid.hesed.sbs`)
- traefik.http.routers.<id>-websecure.entrypoints=websecure
- traefik.http.services.<id>-websecure.loadbalancer.server.port=9501
- traefik.http.routers.<id>-websecure.tls.certresolver=letsencrypt
```
> **⚠️ IMPORTANT:**
> You do **NOT** need to add these labels manually in your `docker-compose.yml`.
> Dokploy handles them when you configure the domain in the UI.
> Adding them manually can cause **label conflicts**.
---
## Step 5: Create the Database
The MySQL database must be created manually after the MySQL service is running.
**Via Hostinger server terminal:**
```bash
# Connect to MySQL container
docker exec -it $(docker ps --format '{{.Names}}' | grep prodsqlmain) mysql -u root -p
```
**Inside MySQL shell:**
```sql
CREATE DATABASE bukid;
GRANT ALL PRIVILEGES ON bukid.* TO 'mysql'@'%';
FLUSH PRIVILEGES;
EXIT;
```
---
## Step 6: Run Migrations & Seed
**Via Dokploy UI → your service → Terminal:**
```bash
php artisan migrate
php artisan db:seed
```
**Or from the Hostinger server terminal:**
```bash
docker exec <bukidapp-container-name> php artisan migrate
docker exec <bukidapp-container-name> php artisan db:seed
```
---
## Deploying Test/Beta Variants
To deploy a test or beta version of the same app with separate databases:
### 1. Plan Variant Resources
| Variant | MySQL DB Name | Redis DB | Domain |
|---------|---------------|----------|---------------------------|
| Prod | `bukid` | `0` | `bukid.hesed.sbs` |
| Beta | `bukid_beta` | `1` | `beta.bukid.hesed.sbs` |
| Test | `bukid_test` | `2` | `test.bukid.hesed.sbs` |
> **💡 TIP:**
> You can reuse the **same MySQL server** and **same DragonflyDB instance** for all variants.
> Just create different databases in MySQL and use different `REDIS_DB` numbers (0-15).
### 2. Create Additional MySQL Databases
**Via Hostinger server terminal:**
```bash
docker exec -it $(docker ps --format '{{.Names}}' | grep prodsqlmain) mysql -u root -p
```
```sql
CREATE DATABASE bukid_beta;
CREATE DATABASE bukid_test;
GRANT ALL PRIVILEGES ON bukid_beta.* TO 'mysql'@'%';
GRANT ALL PRIVILEGES ON bukid_test.* TO 'mysql'@'%';
FLUSH PRIVILEGES;
EXIT;
```
### 3. Deploy New Compose Project in Dokploy
1. In Dokploy UI → **Create****Compose**
2. Connect to the same Git repo, optionally use a different branch (e.g., `beta`, `develop`)
3. Set environment variables with variant-specific values:
```env
DB_DATABASE=bukid_beta
REDIS_DB=1
# DB_HOST and REDIS_HOST remain the same (same infrastructure)
```
4. Configure domain in Domains tab: `beta.bukid.hesed.sbs` → port `9501`
5. Deploy
6. Run migrations via Dokploy container terminal:
```bash
php artisan migrate
php artisan db:seed
```
### 4. Verify Each Variant
**Via Hostinger server terminal:**
```bash
# Find all bukid containers
docker ps --format '{{.Names}}' | grep bukid
# Check health of each — must show (healthy)
docker ps | grep bukid
```
---
## Troubleshooting
### 404 Not Found (Domain Access)
**Check this in order:**
1. **Container health** — Must show `(healthy)`, NOT `(unhealthy)`
```bash
docker ps | grep bukidapp
```
2. **If unhealthy**, check app logs:
```bash
docker logs <container-name> --tail 100
```
3. **Most common cause**: Redis or MySQL DNS resolution failure → wrong hostname in env vars
4. **Network**: Verify container is on `dokploy-network`:
```bash
docker network inspect dokploy-network --format '{{range .Containers}}{{.Name}} {{end}}' | tr ' ' '\n' | grep bukid
```
5. **Redeploy**: Domain/label changes require a full **Redeploy** in Dokploy UI, not just restart
### Redis DNS Lookup Failed
```
RedisException: DNS Lookup resolve failed
```
- **Cause**: Wrong `REDIS_HOST` value
- **Fix**: Use full container name → `docker ps --format '{{.Names}}' | grep dragonfly`
### MySQL Access Denied
```
SQLSTATE[HY000] [1044] Access denied for user 'mysql'@'%' to database 'bukid'
```
- **Cause**: Database doesn't exist or user lacks permissions
- **Fix**: Create the database and grant permissions (see [Step 5](#step-5-create-the-database))
### Traefik Logs Empty
```bash
docker logs dokploy-traefik --tail 50
```
If empty, Traefik hasn't encountered errors — the issue is likely the container being unhealthy.
### Container Keeps Restarting
Check logs for startup errors:
```bash
docker logs <container-name> --tail 200
```
Common causes:
- Missing `.env` variables
- Database not reachable
- Redis not reachable
- PHP extension missing
---
## Reference: Working Configuration Files
### `docker-compose.yml` (committed in repo root)
```yaml
services:
bukidapp:
build:
context: .
dockerfile: Dockerfile.php
ports:
- 9501
networks:
- dokploy-network
healthcheck:
test: [ "CMD-SHELL", "curl -f http://localhost:9501/health || exit 1" ]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
dokploy-network:
external: true
```
### `Dockerfile.php`
```dockerfile
FROM hyperf/hyperf:8.3-alpine-v3.19-swoole-v6
USER root
RUN apk add --no-cache \
postgresql-dev \
php83-pdo_pgsql \
php83-pgsql \
7zip
RUN apk add --no-cache curl \
&& curl -fsSL https://unofficial-builds.nodejs.org/download/release/v20.19.0/node-v20.19.0-linux-x64-musl.tar.gz \
| tar -xz -C /usr/local --strip-components=1 \
&& node -v \
&& npm -v
RUN echo "extension=pdo_pgsql.so" > /etc/php83/conf.d/01_pdo_pgsql.ini && \
echo "extension=pgsql.so" > /etc/php83/conf.d/00_pgsql.ini
WORKDIR /var/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 9501
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader
CMD ["php", "artisan", "serve"]
```
### Required Environment Variables
```env
# Database
DB_CONNECTION=mysql
DB_HOST=<mysql-swarm-service-name>
DB_DATABASE=bukid
DB_PORT=3306
DB_USERNAME=mysql
DB_PASSWORD=<password>
# Redis / DragonflyDB
CACHE_DRIVER=redis
REDIS_HOST=<dragonflydb-container-name>
REDIS_AUTH=<password>
REDIS_PORT=6379
REDIS_DB=0
```
### `/health` Route (in `routes/web.php`)
```php
Route::get('/health', function () {
return 'OK';
});
```
---
## Quick Reference: Useful Commands
All commands are run on the **Hostinger server terminal** unless otherwise noted.
```bash
# List all running containers
docker ps
# Find a specific container name
docker ps --format '{{.Names}}' | grep <keyword>
# List Swarm services (for Dokploy Databases)
docker service ls
# Check container health
docker ps | grep <container>
# View container logs
docker logs <container-name> --tail 100
# Inspect dokploy-network's connected containers
docker network inspect dokploy-network --format '{{range .Containers}}{{.Name}} {{end}}'
# Test connectivity (run in Dokploy container terminal via UI)
ping -c 2 <target-hostname>
# Run artisan commands (via Dokploy container terminal via UI)
php artisan migrate
php artisan db:seed
# Connect to MySQL (via Hostinger server terminal)
docker exec -it $(docker ps --format '{{.Names}}' | grep prodsqlmain) mysql -u root -p
```

View File

@@ -0,0 +1,86 @@
# System Scan & Feature Recommendations
This document outlines recommended changes, improvements, and new features for the BukidBountyApp based on recent system-wide scans and development patterns.
## 🏗️ Architecture & Technical Improvements
### 1. Unified API Response & Error Handling [HIGH PRIORITY]
- **Current State**: `executeRequest.js` is a basic fetch wrapper. Axios is used directly in stores without global interceptors.
- **Recommendation**: Transition all API calls to a centralized Axios instance with global interceptors.
- **Action**: Implement a standard response structure `{ success: boolean, data: any, message: string }`. Automatically handle 401 (Logout), 403 (Forbidden), and 500 (Server Error) via global toast notifications.
- **Benefit**: Reduces boilerplate in components and ensures consistent error feedback.
### 2. Model Boot Traits / HasStandardFields
- **Current State**: `ModelSavingListener` handles `hashkey`, `created_by`, and `updated_by` globally.
- **Recommendation**: Move this logic to a `HasStandardFields` trait.
- **Benefit**: Cleaner model definitions and avoids global listener overhead for models that don't need these fields.
### 3. Real-time Synchronization (WebSockets)
- **Recommendation**: Integrate WebSockets for live updates.
- **Targets**:
- Real-time sale notifications for Store Owners.
- Live shipment status updates for Buyers.
- POS session updates across multiple terminals.
- **Benefit**: Makes the application feel alive and reduces manual refreshing.
### 4. PWA & Offline Support (Advanced)
- **Current State**: `PosMain.vue` has basic local storage for access keys, but no offline transaction capability.
- **Recommendation**: Implement IndexedDB for `PosMain.vue` to allow full offline operation.
- **Action**: Store product catalog and pending transactions locally. Auto-sync when back online using Background Sync API.
- **Benefit**: Critical for farm locations with poor connectivity.
### 5. Defensive Middleware & Null Safety
- **Recommendation**: Audit all middleware (especially `EnsureUserGroupIsActive`) for null checks.
- **Benefit**: Prevents "Call to a member function on null" errors during user role/group transitions.
---
## 💎 UI/UX Enhancements
### 1. Skeleton Screens [PENDING]
- **Current State**: `HomeUltimate.vue` and others use a generic `LoadingSpinner`.
- **Recommendation**: Replace spinners with content-matching Skeleton Loaders.
- **Benefit**: Improves perceived performance and reduces layout shift (CLS).
### 2. Premium Micro-animations
- **Recommendation**: Add subtle transitions for page changes and modal entries.
- **Action**: Implement "Success" Lottie animations for completed transactions in POS.
- **Benefit**: Increases user satisfaction and perceived app quality.
### 3. Dynamic Dashboard Activity
- **Current State**: `HomeUltimate.vue` recent activity is hardcoded placeholders.
- **Recommendation**: Connect the "Recent System Activity" list to real audit logs or transaction history.
- **Benefit**: Provides immediate value and transparency to administrators.
### 4. Compact Mode for Data Tables
- **Recommendation**: Add a "Compact Mode" toggle in User Settings for dense lists (Users, Transactions).
- **Benefit**: Improves productivity for power users managing large datasets.
---
## 🚀 New Feature Recommendations
### 1. POS "Quick-Clear" & Reset [COMPLETED]
- **Description**: A dedicated button to completely reset the current POS cart with a confirmation modal.
- **Status**: Implemented via `/api/pos/clear` and `PosMain.vue` UI.
### 2. QR-Based Logistics Tracking
- **Description**: Scan QR codes at each stage of the shipment lifecycle (Picked up -> In Transit -> Delivered).
- **Benefit**: Enhances trust and transparency in the supply chain.
### 3. Advanced Ultimate Tools
- **Description**: Add Redis Memory Monitor and live SQL profiling to the Ultimate Console.
- **Benefit**: Empower system admins to diagnose performance issues in real-time.
### 4. Farmer-to-Store Bidding Board
- **Description**: A digital board where stores post needs and farmers bid to fulfill them.
- **Benefit**: Bridges the gap between production and retail demand.
---
## 📈 Roadmap Suggestions
1. **Phase 1 (Stability)**: Unified API Handling, `HasStandardFields` Trait, Middleware Null-Safety.
2. **Phase 2 (Experience)**: Skeleton Screens, Dynamic Activity Logs, POS Reset Button.
3. **Phase 3 (Logistics)**: QR Shipment Tracking, Offline POS (IndexedDB).
4. **Phase 4 (Marketplace)**: Bidding Board, Advanced Ultimate Monitoring.

49
ai-docs/file-map.json Normal file
View File

@@ -0,0 +1,49 @@
{
"auth": [
"resources/js/Pages/Auth/Login.vue",
"app/Http/Controllers/LoginController.php"
],
"userManagement": [
"resources/js/Pages/CreateUser.vue",
"resources/js/Pages/EditUser.vue",
"resources/js/Pages/UserList.vue",
"app/Http/Controllers/UserCreateController.php",
"app/Http/Controllers/UserManagement/CreateUserControllerUltimate.php",
"app/Http/Controllers/Pages/UserModifyAdminPageController.php",
"app/Http/Controllers/Pages/UserListPageController.php"
],
"market": [
"resources/js/Pages/BuyViewProductMarket.vue",
"resources/js/Pages/EditProductUltimate.vue",
"resources/js/Pages/ListProductsMarket.vue",
"resources/js/Pages/ListStores.vue",
"resources/js/Pages/RemoveProductFromStoreAdmin.vue",
"resources/js/Pages/ViewStoreMarket.vue",
"app/Http/Controllers/Market/ProductController.php",
"app/Http/Controllers/Market/StoreController.php"
],
"home": [
"resources/js/Pages/Home.vue",
"resources/js/Pages/Fragments/Home/HomePublic.vue",
"resources/js/Pages/Fragments/Home/HomeUltimate.vue",
"app/Http/Controllers/Pages/Core/HomeController.php"
],
"accountSettings": [
"resources/js/Pages/AccountSettings.vue",
"app/Http/Controllers/Pages/AccountSettingsPageController.php"
],
"transferCredits": [
"resources/js/Pages/TransferMyCredit.vue",
"app/Http/Controllers/Pages/TransferMyCreditPageController.php"
],
"fileManagement": [
"app/Http/Controllers/FilesMainController.php",
"app/Models/FileContent.php",
"app/Models/FileList.php"
],
"pages": [
"resources/js/Pages/Fragments/Home/*.vue",
"app/Http/Controllers/viewHelperController.php",
"app/Http/Controllers/PageMemoryController.php"
]
}

View File

@@ -0,0 +1,35 @@
# LoginController.php
## Purpose
Handles user authentication including login, session extension, and logout operations.
## Key Components
- `LoginController` class
- `authenticate()` method
- `extendcurrentSession()` method
## Exported Interfaces
- `authenticate()` - Authenticate user via JWT
- `extendcurrentSession()` - Extend current session lifetime
## Inputs
- `$request` - HTTP request with username/password
- Session data for token validation
## Outputs
- Redirect to home page on success
- JSON response on failure
- Session creation on successful authentication
## Side Effects
- Creates JWT-based session
- Sets session cookie
## Dependencies
- `Hypervel\Support\Facades\Auth`
- `App\Models\User`
## Notes
- Uses JWT authentication via Hypervel framework
- Validates credentials against database
- Supports both web and API authentication

View File

@@ -0,0 +1,42 @@
# UserModifyAdminPageController.php
## Purpose
Provides admin-level user modification and management capabilities including enabling/disabling users, password resets, role changes, and session management.
## Key Components
- `UserModifyAdminPageController` class
- `PageResponses_UserModify` trait
## Exported Interfaces
- `Response_UserDetails()` - Get user details by hash key
- `Response_DisableUser()` / `EnableUser()` - Toggle user active status
- `Response_DeleteUser()` - Delete user record
- `Response_ResetUserPassword()` - Reset user password
- `Response_LogoutUser()` - Force logout user session
## Inputs
- `$hashkey` - User hash key identifier
- `$details` - Array of user details to update
- `$newPassword` - New password string
- `$active` - Boolean for active status
- `$request` - Hypervel HTTP request object
## Outputs
- JSON responses with success/error data
- User detail arrays or error messages
## Side Effects
- Updates user records in database
- Invalidates Redis sessions on force logout
- Modifies user password hashes
## Dependencies
- `App\Enums\UserActions`
- `App\Http\Controllers\Helpers\Permissions\UserPermissions`
- `App\Models\User`
- `Hypervel\Support\Facades\Auth`, `Hash`, `Redis`, `Cache`
## Notes
- Uses trait pattern for response handlers
- Permission checks via `UserPermissions::isActionPermitted()`
- Supports hierarchical user relationships (parent/children)

40
ai-docs/files/app.md Normal file
View File

@@ -0,0 +1,40 @@
# app.js
## Purpose
Main Vue application entry point that sets up the SPA (Single Page Application) architecture with global state management and component loading.
## Key Components
- `createApp()` - Vue app instance creation
- `Pinia` - State management (replaces Vuex)
- `useUserStore()` - User state management
- `useAuth()` - Authentication composable
## Exported Interfaces
- `$navigate` - Global navigation helper
- `$user` - User store access
- `$auth` - Auth composable access
- `$modal` - Modal dialog access
## Inputs
- Server-side page data via `dataset.page`
- Component imports from `resources/js/Pages/`
## Outputs
- Vue app mounted to `#app` element
- Global navigation via `$navigate` helper
- Async component loading based on current route
## Side Effects
- Initializes Pinia state management
- Fetches current user data on startup
- Registers global components (TopHeader, BottomNav)
## Dependencies
- `createApp`, `h`, `defineAsyncComponent` from Vue 3
- `Pinia` for state management
- Axios for API calls
## Notes
- Uses Pinia instead of Vuex for state management
- Async component loading improves performance
- Global `$navigate` helper available for programmatic navigation

View File

@@ -0,0 +1,40 @@
# viewHelperController.php
## Purpose
Central controller for page rendering and Vue component delivery. Handles both server-side rendering with templates and client-side SPA navigation.
## Key Components
- `viewHelperController` class
- `servePageFragmentUnified()` method
- `getDefaultDataVariables()` method
- `getAllViews()` method
## Exported Interfaces
- `servePageFragment()` - Render page without template
- `servePageFragmentWithTemplate()` - Render page with layout template
- `getDefaultDataVariables()` - Get default view variables
- `getAllViews()` - Get all available blade views
## Inputs
- `$page` - Page identifier (route name)
- `$data` - Data payload for the page
- `$withTemplate` - Boolean to include layout template
## Outputs
- HTML response with embedded navigation script
- Base64-encoded HTML for SPA
- View variables including currentUser and userModifyAdmin
## Side Effects
- Renders blade templates
- Returns base64-encoded HTML for Vue navigation
## Dependencies
- `App\Enums\UserTypes`
- `Hypervel\Support\Facades\Auth`, `Response`, `File`
- `App\Http\Controllers\Pages\Core\ApplicationController`
## Notes
- Supports public and authenticated routes
- Uses viewMap config for page-to-view mapping
- Returns base64-encoded HTML for SPA navigation

287
ai-docs/function-index.json Normal file
View File

@@ -0,0 +1,287 @@
{
"authenticate": {
"file": "app/Http/Controllers/LoginController.php",
"module": "auth",
"type": "function"
},
"extendcurrentSession": {
"file": "app/Http/Controllers/LoginController.php",
"module": "auth",
"type": "function"
},
"setSessiontoKeepAlive": {
"file": "app/Http/Controllers/LoginController.php",
"module": "auth",
"type": "method"
},
"servePageFragment": {
"file": "app/Http/Controllers/viewHelperController.php",
"module": "pages",
"type": "function"
},
"servePageFragmentWithTemplate": {
"file": "app/Http/Controllers/viewHelperController.php",
"module": "pages",
"type": "function"
},
"servePageFragmentUnified": {
"file": "app/Http/Controllers/viewHelperController.php",
"module": "pages",
"type": "function"
},
"getDefaultDataVariables": {
"file": "app/Http/Controllers/viewHelperController.php",
"module": "pages",
"type": "method"
},
"getAllViews": {
"file": "app/Http/Controllers/viewHelperController.php",
"module": "pages",
"type": "function"
},
"decodeData": {
"file": "app/Http/Controllers/viewHelperController.php",
"module": "pages",
"type": "method"
},
"tryjsondecode": {
"file": "app/Http/Controllers/viewHelperController.php",
"module": "pages",
"type": "method"
},
"tryjsonencode": {
"file": "app/Http/Controllers/viewHelperController.php",
"module": "pages",
"type": "method"
},
"uploadFilefromRequest": {
"file": "app/Http/Controllers/FilesMainController.php",
"module": "files",
"type": "function"
},
"viewFilebyFileListHash": {
"file": "app/Http/Controllers/FilesMainController.php",
"module": "files",
"type": "function"
},
"createUser": {
"file": "app/Http/Controllers/UserCreateController.php",
"module": "userManagement",
"type": "function"
},
"Response_ListChildrenofCurrentUser": {
"file": "app/Http/Controllers/Pages/UserListPageController.php",
"module": "userManagement",
"type": "function"
},
"Response_UserDetails": {
"file": "app/Http/Controllers/Pages/UserModifyAdminPageController.php",
"module": "userManagement",
"type": "function"
},
"Response_childrenofTargetUser": {
"file": "app/Http/Controllers/Pages/UserModifyAdminPageController.php",
"module": "userManagement",
"type": "function"
},
"Response_DisableUser": {
"file": "app/Http/Controllers/Pages/UserModifyAdminPageController.php",
"module": "userManagement",
"type": "function"
},
"Response_EnableUser": {
"file": "app/Http/Controllers/Pages/UserModifyAdminPageController.php",
"module": "userManagement",
"type": "function"
},
"Response_DeleteUser": {
"file": "app/Http/Controllers/Pages/UserModifyAdminPageController.php",
"module": "userManagement",
"type": "function"
},
"Response_ViewNotes": {
"file": "app/Http/Controllers/Pages/UserModifyAdminPageController.php",
"module": "userManagement",
"type": "function"
},
"Response_DeleteNotes": {
"file": "app/Http/Controllers/Pages/UserModifyAdminPageController.php",
"module": "userManagement",
"type": "function"
},
"Response_ReplaceNotes": {
"file": "app/Http/Controllers/Pages/UserModifyAdminPageController.php",
"module": "userManagement",
"type": "function"
},
"Response_UpdateUserDetails": {
"file": "app/Http/Controllers/Pages/UserModifyAdminPageController.php",
"module": "userManagement",
"type": "function"
},
"Response_ResetUserPassword": {
"file": "app/Http/Controllers/Pages/UserModifyAdminPageController.php",
"module": "userManagement",
"type": "function"
},
"Response_LogoutUser": {
"file": "app/Http/Controllers/Pages/UserModifyAdminPageController.php",
"module": "userManagement",
"type": "function"
},
"Response_ChangeUserRoles": {
"file": "app/Http/Controllers/Pages/UserModifyAdminPageController.php",
"module": "userManagement",
"type": "function"
},
"CreateUser": {
"file": "app/Http/Controllers/UserManagement/CreateUserControllerUltimate.php",
"module": "userManagement",
"type": "function"
},
"listAllUserTypesforSelectHTML": {
"file": "app/Http/Controllers/UserManagement/CreateUserControllerUltimate.php",
"module": "userManagement",
"type": "function"
},
"listAllUsersforParentSelectHTML": {
"file": "app/Http/Controllers/UserManagement/CreateUserControllerUltimate.php",
"module": "userManagement",
"type": "function"
},
"checkIfUserMobileNumberExists": {
"file": "app/Http/Controllers/UserManagement/CreateUserControllerUltimate.php",
"module": "userManagement",
"type": "function"
},
"checkIfUsernameExists": {
"file": "app/Http/Controllers/UserManagement/CreateUserControllerUltimate.php",
"module": "userManagement",
"type": "function"
},
"listDetails": {
"file": "app/Http/Controllers/Pages/AccountSettingsPageController.php",
"module": "accountSettings",
"type": "function"
},
"listSettings": {
"file": "app/Http/Controllers/Pages/AccountSettingsPageController.php",
"module": "accountSettings",
"type": "function"
},
"changepassword": {
"file": "app/Http/Controllers/Pages/AccountSettingsPageController.php",
"module": "accountSettings",
"type": "function"
},
"listRunScripts": {
"file": "app/Http/Controllers/Pages/AccountSettingsPageController.php",
"module": "accountSettings",
"type": "function"
},
"getUserNotes": {
"file": "app/Http/Controllers/Pages/AccountSettingsPageController.php",
"module": "accountSettings",
"type": "function"
},
"clearUserNotes": {
"file": "app/Http/Controllers/Pages/AccountSettingsPageController.php",
"module": "accountSettings",
"type": "function"
},
"logoutnow": {
"file": "app/Http/Controllers/Pages/AccountSettingsPageController.php",
"module": "accountSettings",
"type": "function"
},
"Response_TransferMyCredit": {
"file": "app/Http/Controllers/Pages/TransferMyCreditPageController.php",
"module": "transferCredits",
"type": "function"
},
"createNew_Admin": {
"file": "app/Http/Controllers/Market/ProductController.php",
"module": "market",
"type": "function"
},
"editProductAdmin": {
"file": "app/Http/Controllers/Market/ProductController.php",
"module": "market",
"type": "function"
},
"AddProducttoStore": {
"file": "app/Http/Controllers/Market/ProductController.php",
"module": "market",
"type": "function"
},
"RemoveProductFromStore": {
"file": "app/Http/Controllers/Market/ProductController.php",
"module": "market",
"type": "function"
},
"viewProductDetails": {
"file": "app/Http/Controllers/Market/ProductController.php",
"module": "market",
"type": "function"
},
"listProductsData": {
"file": "app/Http/Controllers/Market/ProductController.php",
"module": "market",
"type": "function"
},
"getCategories": {
"file": "app/Http/Controllers/Market/ProductController.php",
"module": "market",
"type": "function"
},
"getSubcategories": {
"file": "app/Http/Controllers/Market/ProductController.php",
"module": "market",
"type": "function"
},
"viewProductDetailsByStoreEdit": {
"file": "app/Http/Controllers/Market/ProductController.php",
"module": "market",
"type": "function"
},
"viewProductwithAddStoreData": {
"file": "app/Http/Controllers/Market/ProductController.php",
"module": "market",
"type": "function"
},
"store": {
"file": "app/Http/Controllers/Market/StoreController.php",
"module": "market",
"type": "function"
},
"update": {
"file": "app/Http/Controllers/Market/StoreController.php",
"module": "market",
"type": "function"
},
"viewStoreDetails": {
"file": "app/Http/Controllers/Market/StoreController.php",
"module": "market",
"type": "function"
},
"editStoreDetails": {
"file": "app/Http/Controllers/Market/StoreController.php",
"module": "market",
"type": "function"
},
"listStoresActiveDataAll": {
"file": "app/Http/Controllers/Market/StoreController.php",
"module": "market",
"type": "function"
},
"removeProductfromStore": {
"file": "app/Http/Controllers/Market/StoreController.php",
"module": "market",
"type": "function"
},
"handle": {
"file": "app/Http/Controllers/Photos/PhotoGallery.php",
"module": "photos",
"type": "function"
}
}

View File

@@ -0,0 +1,23 @@
# Account Settings Module
## Purpose
Manages user account settings including password changes, notes, and script execution.
## Key Files
- `app/Http/Controllers/Pages/AccountSettingsPageController.php` - Controller
- `resources/js/Pages/AccountSettings.vue` - Vue component
## Public APIs
- `listDetails()` - List account details
- `changepassword()` - Change user password
- `logoutnow()` - Logout current user
- `getUserNotes()` / `clearUserNotes()` - Note management
## Dependencies
- `App\Models\User`
- `Hypervel\Support\Facades\Auth`
## Important Behavior
- Passwords are encrypted using bcrypt
- Notes stored in user's notes field
- Scripts can be executed via exec_command field

24
ai-docs/modules/auth.md Normal file
View File

@@ -0,0 +1,24 @@
# Auth Module
## Purpose
Handles user authentication, login, session management, and logout functionality. This module provides the foundation for all authenticated routes in the application.
## Key Files
- `resources/js/Pages/Auth/Login.vue` - Vue login component
- `app/Http/Controllers/LoginController.php` - Authentication controller
- `app/Models/User.php` - User model with authentication
## Public APIs
- `authenticate()` - Authenticate user via JWT
- `extendcurrentSession()` - Extend current session lifetime
- `setSessiontoKeepAlive()` - Keep session alive
## Dependencies
- `Hypervel\Support\Facades\Auth`
- `App\Http\Controllers\Pages\Core\ApplicationController`
## Important Behavior
- Uses JWT-based authentication
- Validates user credentials against database
- Creates session upon successful login
- Redirects to home page or returns JSON response based on request type

View File

@@ -0,0 +1,71 @@
# Module Planning: Cooperative Module
## Overview
The Cooperative Module manages associations of users (members) who work together, typically in agriculture. It integrates deeply with `user_infos` to provide a complete picture of each member.
## Data Models
### `Organization` (existing)
Used to store the cooperative's main details.
- `id` (Primary Key)
- `hashkey` (String, Unique)
- `name` (String)
- `type` (Enum: `COOPERATIVE`, `ASSOCIATION`, `COMPANY`)
- `address` (Text)
- `is_active` (Boolean)
### `CooperativeMember` [NEW]
Links users to cooperatives.
- `id` (Primary Key)
- `hashkey` (String, Unique)
- `organization_id` (Foreign Key to `organizations`)
- `user_id` (Foreign Key to `users`)
- `role` (String: `MEMBER`, `OFFICER`, `ADMIN`)
- `joined_at` (DateTime)
- `is_active` (Boolean)
- `created_by` (Foreign Key to `users`)
- `updated_by` (Foreign Key to `users`)
### `UserInfo` [NEW]
Detailed personal information for users, expanding the core `users` table.
- `id` (Primary Key)
- `hashkey` (String, Unique)
- `user_id` (Foreign Key to `users`)
- `firstname` (String)
- `middlename` (String)
- `lastname` (String)
- `fullname` (String) - Generated or stored
- `landline` (String)
- `mobile` (String)
- `email` (String)
- `alt_email` (String)
- `alt_landline` (String)
- `alt_mobile` (String)
- `facebook_url` (String)
- `bank_details` (JSON)
- `addresses` (JSON) - Array of address objects
- `other_details` (JSON)
- `is_active` (Boolean)
- `created_by` (Foreign Key to `users`)
- `updated_by` (Foreign Key to `users`)
## Core Workflows
1. **Cooperative Management**: Creating and updating cooperative profiles (using `organizations` table).
2. **Member Enrollment**: Adding users to a cooperative and assigning roles.
3. **User Profile Completion**: Linking users to their detailed `userinfo` record.
## API Endpoints (Proposed)
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/cooperatives` | GET | List all cooperatives |
| `/api/cooperatives/{hashkey}/members` | GET | List members of a cooperative |
| `/api/cooperatives/{hashkey}/join` | POST | Request to join or add a member |
| `/api/user-info/{user_hashkey}` | GET | Get detailed user info |
| `/api/user-info/{user_hashkey}` | POST/PUT | Update detailed user info |
## UI Components (Vue)
- **CooperativeList.vue**: Standard list of cooperatives.
- **CooperativeDetail.vue**: detailed view with member list.
- **UserInfoEdit.vue**: Comprehensive form for editing personal details.

View File

@@ -0,0 +1,48 @@
# Module Planning: Supplier/Farmer Management
## Overview
This module expands the basic member information into a full profile for producers (farmers/suppliers). It allows the system to track where products come from, verify farmer credentials, and manage relationships with cooperatives.
## Data Models
### `FarmerProfile`
Extended details for a producer.
- `id` (Primary Key)
- `hashkey` (String, Unique)
- `user_id` (Foreign Key to `users`)
- `organization_id` (Foreign Key to `organizations/cooperatives`, nullable)
- `farm_name` (String)
- `farm_location` (Point/Coordinates or Text)
- `main_crops` (JSON/Array of strings)
- `verification_status` (Enum: `UNVERIFIED`, `PENDING`, `VERIFIED`, `REJECTED`)
- `certification_details` (JSON)
- `created_by` (Foreign Key to `users`)
- `updated_by` (Foreign Key to `users`)
- `is_active` (Boolean)
### `FarmerProductMapping`
Links farmers to the specific products they supply to stores.
- `farmer_id` (Foreign Key to `farmer_profiles`)
- `product_id` (Foreign Key to `products`)
- `supply_capacity` (Decimal/Unit)
- `harvest_season` (String)
## Core Workflows
1. **Farmer Registration**: Capturing the detailed "Member Information" as seen in the README.
2. **Profile Verification**: Admin review of submitted documents and farm location.
3. **Product Sourcing**: Linking products in the store to their original farmer sources for traceability.
## API Endpoints (Proposed)
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/farmers` | GET | List all farmers (filtered by org/admin) |
| `/api/farmers` | POST | Register a new farmer profile |
| `/api/farmers/{hashkey}` | GET | Get farmer details |
| `/api/farmers/{hashkey}/verify` | PATCH | Update verification status |
## UI Components (Vue)
- **FarmerProfileEdit.vue**: Comprehensive form for farmer details.
- **FarmerDirectory.vue**: Searchable list of registered farmers.
- **VerificationDashboard.vue**: Admin interface for approving farmer applications.

23
ai-docs/modules/files.md Normal file
View File

@@ -0,0 +1,23 @@
# Files Module
## Purpose
Handles file uploads and management operations including photos, documents, and binary files.
## Key Files
- `app/Http/Controllers/FilesMainController.php` - File upload controller
- `app/Models/FileContent.php` - File content model
- `app/Models/FileList.php` - File list metadata model
## Public APIs
- `uploadFilefromRequest()` - Upload file from request
- `viewFilebyFileListHash()` - View file by hash key
## Dependencies
- `App\Models\FileContent`
- `Hypervel\Support\Facades\Storage`
## Important Behavior
- Stores files in binary format via FileContent model
- Tracks metadata in FileList model
- Supports various file categories
- Returns file content with proper headers for download/viewing

23
ai-docs/modules/home.md Normal file
View File

@@ -0,0 +1,23 @@
# Home Module
## Purpose
Provides the main application shell and home page functionality. Includes layout fragments, top header, and bottom navigation.
## Key Files
- `resources/js/Pages/Home.vue` - Main home component
- `resources/js/Pages/Fragments/Home/HomePublic.vue` - Public home fragment
- `resources/js/Pages/Fragments/Home/HomeUltimate.vue` - Ultimate user home fragment
- `app/Http/Controllers/Pages/Core/HomeController.php`
## Public APIs
- `render()` - Renders the main application shell
## Dependencies
- `TopHeader` - Header component
- `BottomNav` - Bottom navigation component
- `BaseModal` - Modal component
## Important Behavior
- Uses Pinia stores for state management (user, UI, network)
- Async component loading based on current page
- Global navigate helper available via `$navigate`

28
ai-docs/modules/market.md Normal file
View File

@@ -0,0 +1,28 @@
# Market Module
## Purpose
Manages product and store operations for the multi-vendor marketplace. Handles product CRUD, store management, and transaction tracking.
## Key Files
- `app/Http/Controllers/Market/ProductController.php` - Product controller
- `app/Http/Controllers/Market/StoreController.php` - Store controller
- `app/Models/Market/Product.php` - Product model
- `app/Models/Market/Store.php` - Store model
## Public APIs
- `createNew_Admin()` - Create product for admin
- `editProductAdmin()` - Edit product
- `AddProducttoStore()` / `RemoveProductFromStore()`
- `store()` - Create new store
- `update()` - Update store
## Dependencies
- `App\Models\Market\Product`
- `App\Models\Market\Store`
- `App\Models\User`
## Important Behavior
- Products can belong to multiple stores (belongsToMany)
- Supports categories and subcategories via enums
- Tracks transaction sessions per store
- Store-product relationship with pivot data (available, price, is_active)

25
ai-docs/modules/pages.md Normal file
View File

@@ -0,0 +1,25 @@
# Pages Module
## Purpose
Handles page rendering and server-side Vue component delivery. Provides unified approach for both SPA (client-side) and traditional server-rendered pages.
## Key Files
- `app/Http/Controllers/viewHelperController.php` - Main page controller
- `resources/js/Pages/Fragments/Home/*.vue` - Home page fragments
- `app/Http/Controllers/PageMemoryController.php`
## Public APIs
- `servePageFragment()` - Render page without template
- `servePageFragmentWithTemplate()` - Render page with layout template
- `getDefaultDataVariables()` - Get default view variables
- `getAllViews()` - Get all available views
## Dependencies
- `App\Enums\UserTypes` - Role enum
- `App\Http\Controllers\Pages\Core\ApplicationController`
## Important Behavior
- Routes pages via `/p/{page}/s/` pattern
- Uses viewMap config for page-to-view mapping
- Supports public and authenticated routes
- Returns base64-encoded HTML for SPA navigation

View File

@@ -0,0 +1,56 @@
# Module Planning: Shipment & Logistics
## Overview
The Shipment Module handles the movement of products from stores to customers. It tracks the status of deliveries, manages courier information, and provides updates to both sellers and buyers.
## Data Models
### `Shipment`
Tracks an individual delivery process.
- `id` (Primary Key)
- `hashkey` (String, Unique)
- `transaction_id` (Foreign Key to `global_transactions` or `pos_transactions`)
- `store_id` (Foreign Key to `stores`)
- `customer_id` (Foreign Key to `cst`)
- `courier_id` (Foreign Key to `couriers`, nullable)
- `tracking_number` (String, Unique)
- `status` (Enum: `PENDING`, `PICKED_UP`, `IN_TRANSIT`, `DELIVERED`, `FAILED`, `RETURNED`)
- `origin_address` (Text)
- `destination_address` (Text)
- `estimated_delivery_date` (DateTime)
- `actual_delivery_date` (DateTime)
- `shipping_fee` (Decimal)
- `created_by` (Foreign Key to `users`)
- `updated_by` (Foreign Key to `users`)
- `is_active` (Boolean)
### `Courier`
External or internal delivery services.
- `id` (Primary Key)
- `hashkey` (String, Unique)
- `name` (String, e.g., "J&T Express", "Lalamove", "Local Rider")
- `contact_number` (String)
- `type` (Enum: `EXTERNAL`, `INTERNAL`)
- `is_active` (Boolean)
## Core Workflows
1. **Shipment Creation**: Triggered when an order is confirmed or a "Create Shipment" action is performed in the POS.
2. **Courier Assignment**: Assigning a courier to a pending shipment.
3. **Status Updates**: Manual or API-based updates as the package moves.
4. **Delivery Confirmation**: Final status update and timestamp recording.
## API Endpoints (Proposed)
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/shipments` | GET | List all shipments (filtered by store/user) |
| `/api/shipments` | POST | Create a new shipment |
| `/api/shipments/{hashkey}` | GET | Get shipment details |
| `/api/shipments/{hashkey}/status` | PATCH | Update shipment status |
| `/api/couriers` | GET | List active couriers |
## UI Components (Vue)
- **ShipmentList.vue**: Dashboard for tracking multiple deliveries.
- **ShipmentDetail.vue**: detailed view of a single shipment with timeline.
- **CreateShipmentModal.vue**: Form to initialize a shipment for an order.

View File

@@ -0,0 +1,20 @@
# Transfer Credits Module
## Purpose
Handles credit transfers between users within the marketplace system.
## Key Files
- `app/Http/Controllers/Pages/TransferMyCreditPageController.php` - Controller
- `resources/js/Pages/TransferMyCredit.vue` - Vue component
## Public APIs
- `Response_TransferMyCredit()` - Transfer credits to another user
## Dependencies
- `App\Models\User`
- `Hypervel\Support\Facades\Auth`
## Important Behavior
- Transfers credit balance between users
- Validates user existence before transfer
- Updates total_credit fields for both parties

View File

@@ -0,0 +1,28 @@
# User Management Module
## Purpose
Manages user lifecycle including creation, modification, role assignment, and permission control. Provides hierarchical user role system with 13 distinct roles.
## Key Files
- `app/Http/Controllers/UserManagement/CreateUserControllerUltimate.php` - User creation controller
- `app/Http/Controllers/Pages/UserModifyAdminPageController.php` - Admin user management controller
- `app/Http/Controllers/Pages/UserListPageController.php` - User list controller
- `app/Enums/UserTypes.php` - User role enumeration
## Public APIs
- `CreateUser()` - Create new user with validation
- `Response_ListChildrenofCurrentUser()` - Get user's children
- `Response_UserDetails()` - Get user details by ID
- `Response_DisableUser()` / `EnableUser()` / `DeleteUser()`
- `listAllUserTypesforSelectHTML()` - Get all user types for dropdown
## Dependencies
- `App\Enums\UserActions` - Permission actions
- `App\Http\Controllers\Helpers\Permissions\UserPermissions` - Role permissions
## Important Behavior
- Validates mobile number and username uniqueness
- Creates users with encrypted password
- Supports hierarchical user structure (parent/children)
- Role assignment via `Response_ChangeUserRoles()`
- Permissions checked via `canDo()` method on User model

View File

@@ -0,0 +1,79 @@
# Strategy: Scaling BukidBountyApp for Multiple Companies
This document outlines the recommended approach for deploying BukidBountyApp across different companies while maintaining efficiency and code quality.
## Comparison of Approaches
| Feature | Option 1: Split Repositories | Option 2: Single Codebase (Multi-Instance) |
| :--- | :--- | :--- |
| **Maintenance** | **High Overhead**. A bug fix in one repo must be manually copied to others. | **Low Overhead**. One fix in the main repo benefits all instances. |
| **Consistency** | **Low**. Repos will drift apart over time, making cross-support difficult. | **High**. All companies run the same version of the platform. |
| **Customization** | **Easy**. Just edit the code for that one company. | **Config-Driven**. Use environment variables or flags to toggle features. |
| **Data Privacy** | **Physical Isolation**. Separate repos usually mean separate DBs. | **Physical Isolation**. Each instance connects to its own database. |
| **Scalability** | **Poor**. Adding a 4th company requires another repo and more maintenance. | **Excellent**. Just add a new deployment in Dokploy. |
---
## Proposed Plan: Single Codebase, Multi-Instance
I propose a **Single Codebase** approach using **Environment-Based Deployments**. This is the standard "Best Practice" for SaaS-like applications.
### 1. Repository Management
* Keep **one single repository** for all core logic.
* Use branches (`production`, `staging`) to manage release cycles.
* Every company pulls from the same codebase.
### 2. Deployment Architecture (Dokploy)
Instead of one Dokploy project, you create multiple deployments pointing to the same repository:
* **Instance A**: `company-a.bukid.app`
* **Instance B**: `company-b.bukid.app`
* **Instance C**: `company-c.bukid.app`
### 3. Environment Variables (.env) for Customization
Use the `.env` file for each instance to differentiate the identity and behavior:
```bash
# Instance A .env
APP_NAME="Company A Bounty"
APP_URL="https://company-a.bukid.app"
PRIMARY_COLOR="#4CAF50"
DB_DATABASE="bukid_company_a"
ENABLE_MODULE_FARMER=true
```
```bash
# Instance B .env
APP_NAME="Company B Marketplace"
APP_URL="https://company-b.bukid.app"
PRIMARY_COLOR="#2196F3"
DB_DATABASE="bukid_company_b"
ENABLE_MODULE_FARMER=false
```
### 4. Code Adjustments for Customization
To make the UI "dynamic" based on the company:
* **Identity**: Use `{{ config('app.name') }}` in the frontend (passed via Inertia or API).
* **Theming**: In `app.js` or `index.css`, use CSS variables that can be injected based on environment.
* **Features**: Wrap specific modules (like Farmer Management) in Feature Flags:
```php
// In a Controller or Vue component
if (config('features.farmer_module')) {
// Show farmer features
}
```
### 5. Database Isolation
Since each instance has its own `DB_DATABASE` env variable, the data is physically isolated. This ensures that:
- Company A cannot see Company B's data.
- Migrations are run per instance.
- You can backup/restore Company A without affecting others.
---
## Migration Steps (Advice)
1. **Audit hardcoded strings**: Scan the code for "BukidBounty" and replace them with `config('app.name')`.
2. **Move Colors to variables**: If you have hardcoded colors in CSS, move them to `:root` variables in `index.css` and allow them to be overridden or controlled by the app name.
3. **Setup Dokploy Multi-Project**: Create a second project in Dokploy, use the same SSH key/Repo URL, and configure a second database.
4. **Feature Toggles**: Create a `config/features.php` file that reads from `env()` to enable/disable specific modules for specific companies.
## Conclusion
**Option 2 (Single Codebase)** is the significantly better long-term choice. It prevents "Maintenance Hell" and ensures that as you improve the app for one company, you are improving it for everyone.

View File

@@ -0,0 +1 @@
create a config for preloaders access so every account types has a list of data urls it can preload and we will update sse so that list and view operations in the app do not need to be reloaded and every page load will be seemless since pages are already preloaded and then we will add the sse data preload for data. create a composable a controller and web route create a todo note instead of executing code changes. ensure that the todo doc is very specific even the line that must be changed since another much simpler ai will execute the todo note and code

69
ai-docs/repo-overview.md Normal file
View File

@@ -0,0 +1,69 @@
# Repository Overview
## Purpose
BukidBountyApp is a multi-vendor marketplace application built with Laravel/Hyperf PHP framework and Vue.js. It provides a platform for store owners, suppliers, and users to manage products, transactions, and credits within a hierarchical user system.
## Technologies Used
### Backend
- **PHP 8.2+** - Primary language
- **Hypervel Framework** (Laravel-style) - PHP framework with native coroutine support
- **Hyperf** - High-performance PHP framework components
- **SQLite/MySQL** - Database storage
- **Redis** - Caching and session management
- **JWT** - Authentication
### Frontend
- **Vue 3.5+** - JavaScript framework
- **Pinia** - State management (replaces Vuex)
- **Axios** - HTTP client
## Entry Points
- `/login` - Authentication page
- `/` - Root SPA route (Vue Router handles client-side routing)
- `/p/{page}/s/` - Server-side page rendering with template
- `/sp/login` - Special login route
## Key Subsystems
### User Management
- Hierarchical user roles (13 types from Ultimate to Public)
- User CRUD operations (create, disable, enable, delete)
- Role assignment and permission management
- Credit transfer functionality
### Market/Products
- Product management (CRUD operations)
- Store management
- Product transactions
- Category/subcategory system
- Store-product relationship management
### File Management
- File upload handling
- Photo gallery management
- File list tracking
## High-Level Structure
```
app/
├── Http/Controllers/ # Request handlers
│ ├── Pages/ # Page controllers
│ ├── Market/ # Product/store controllers
│ ├── UserManagement/ # User admin controllers
│ └── Support/ # Utility controllers
├── Models/ # Data models
│ ├── Market/ # Product, Store, Transaction models
│ └── Generic/ # Base model classes
├── Enums/ # Value objects (UserTypes, UserActions)
└── Http/Middleware/ # Request filtering
resources/
├── js/Pages/ # Vue components
│ ├── Auth/ # Authentication pages
│ └── Fragments/ # Layout fragments
└── views/ # Blade templates
routes/ # Route definitions
database/ # Migrations and seeders

View File

@@ -0,0 +1,8 @@
# Checklist: URL Argument and Routing Fixes
- [ ] Verify `resources/js/composables/useUrlArgument.js` regexes match `--h` (no colon) format for hashkey URLs
- [ ] Verify `app/Support/RouteArgumentParser.php` `decodeHashValue` performs `urldecode` after `base64_decode`
- [ ] Verify direct browser URL navigation to a ViewStoreMarket page loads the store correctly (no "Store not found" error)
- [ ] Verify in-app button navigation to ViewStoreMarket still works as before
- [ ] Verify frontend `extractHashkeyFromUrl()` correctly decodes hashkeys from the updated URL format
- [ ] Verify backend `RouteArgumentParser::parseArgument()` correctly decodes hashkeys via the round-trip test

View File

@@ -0,0 +1,10 @@
# Verification Checklist: Fix URL-based Page Navigation
- [ ] Verify that navigating to `CooperativeList` generates `/cooperative-list` in the URL.
- [ ] Verify that navigating to `BuyViewProductMarket` generates `/buy-view-product-market` in the URL.
- [ ] Verify that direct URL access to `/cooperative-list` correctly loads the `CooperativeList` component.
- [ ] Verify that direct URL access to `/list-products-market` correctly loads the `ListProductsMarket` component (check updated backend routes).
- [ ] Verify that URLs with hash parameters like `/edit-user--hHASHKEY` still work correctly.
- [ ] Verify that URLs with payload parameters like `/some-page--e:PAYLOAD` still work correctly.
- [ ] Verify that browser back/forward buttons still work with the new URL format.
- [ ] Verify that page refresh maintains the correct component state.

View File

@@ -0,0 +1,9 @@
# Verification Checklist: Update Cooperative Table Components
- [ ] Verify "Enroll New Farmer" button in `CooperativeDetail` is smaller and not full-width (`w-100` removed).
- [ ] Verify `SearchableTableWrapper` is displayed for the members list in `CooperativeDetail`.
- [ ] Verify search input is present and filters members correctly based on name or role.
- [ ] Verify table density toggle works and adjusts the member table layout.
- [ ] Verify table shows original member information (Full Name, Role).
- [ ] Verify "View Member" functionality (chevron or row click) still navigates correctly to `UserInfoEdit`.
- [ ] Verify "No members found" empty state shows correctly if the search query matches nothing.

View File

@@ -0,0 +1,68 @@
# TODO: Add Backend Interception for Disabled Pages
## Problem Statement
Pages disabled through the Ultimate Console are still accessible via direct URL `/p/{page}/s/{data}`. The `VueRouteMap` already has disabled page checks, but `viewHelperController` does not.
## Implementation Plan
### Step 1: Add Disabled Page Check to viewHelperController
- **File:** `app/Http/Controllers/viewHelperController.php`
- **Method:** `servePageFragmentUnified()`
- **Changes:**
- Add check for `disabled_pages` system setting at the start of the method
- Retrieve disabled pages list using `SystemSetting::getValue('disabled_pages', [])`
- Check if current page name is in the disabled list (case-insensitive)
- If disabled and user is not Ultimate type, return redirect to `/` or 403 error
- Allow Ultimate users to still access disabled pages (for fixing settings)
### Step 2: Add Helper Method for Disabled Page Checking
- **File:** `app/Http/Controllers/viewHelperController.php`
- **New Method:** `isPageDisabled(string $pageName): bool`
- **Purpose:** Centralized logic to check if a page is disabled
- **Logic:**
- Get `disabled_pages` from SystemSetting
- Compare page name case-insensitively
- Return true if disabled, false otherwise
### Step 3: Add Helper Method for Access Permission
- **File:** `app/Http/Controllers/viewHelperController.php`
- **New Method:** `canAccessDisabledPage(): bool`
- **Purpose:** Check if current user can access disabled pages
- **Logic:**
- Check if user is authenticated
- Check if user has Ultimate account type
- Return true only for Ultimate users
### Step 4: Update servePageFragmentUnified Method
- **Location:** After user authentication check, before viewMap lookup
- **Logic Flow:**
1. Check if page is disabled using `isPageDisabled()`
2. If disabled, check if user can access using `canAccessDisabledPage()`
3. If user cannot access, return appropriate response:
- Option A: Redirect to `/` (consistent with VueRouteMap)
- Option B: Return 403 Forbidden with message
4. If user can access (Ultimate), continue normal flow
### Step 5: Handle Edge Cases
- Ensure case-insensitive matching for page names
- Handle null/empty disabled_pages gracefully
- Maintain backward compatibility with existing functionality
- Ensure public pages are not affected by this check
### Step 6: Testing Considerations
- Test with disabled page list containing various page names
- Test with Ultimate user accessing disabled page
- Test with non-Ultimate user accessing disabled page
- Test with empty disabled_pages setting
- Test with case variations in page names
## Files to Modify
1. `app/Http/Controllers/viewHelperController.php` - Main changes
## Dependencies
- `App\Models\SystemSetting` - For retrieving disabled_pages setting
- `App\Enums\UserTypes` - For checking Ultimate user type
- `Hypervel\Support\Facades\Auth` - For user authentication
## Expected Outcome
After implementation, pages disabled in the Ultimate Console will be inaccessible via direct URL `/p/{page}/s/{data}` for non-Ultimate users, while Ultimate users retain access to fix settings.

View File

@@ -0,0 +1,5 @@
# URL Argument and Routing Fixes
- [ ] Update `resources/js/composables/useUrlArgument.js` to correctly match `--h` (no colon) format instead of `--h:` for hashkey URLs.
- [ ] Update `app/Support/RouteArgumentParser.php` in `decodeHashValue` to perform `urldecode` after `base64_decode`, mirroring frontend's `encodeURIComponent`.
- [ ] Test direct navigation to a store market page to verify that the store is successfully found.

View File

@@ -0,0 +1,4 @@
- [ ] Modify `resources/js/stores/ui.js` to ensure `refreshSettings` properly updates `lastSynced`.
- [ ] Update `resources/js/Pages/UltimateConsole.vue` to use `useUIStore` and trigger a sync/refresh after toggling page status.
- [ ] Adjust `resources/js/composables/Core/useNavigate.js` to use a much shorter cache threshold (e.g., 30s) or ensure it always uses the store's freshest state.
- [ ] Verify immediate page access after enabling in the admin console. Build and restart per standard rules.

View File

@@ -0,0 +1,4 @@
- [ ] Fix signature mismatch in `CooperativeController@getCooperative` to retrieve `hashkey` from request body
- [ ] Ensure `ViewOrganizations` permission is properly assigned to the target user role
- [ ] Perform `npm run build` to update compiled assets
- [ ] Restart `bukidbountyapp` Docker container to apply changes

View File

@@ -0,0 +1,5 @@
- [ ] Fix fatal error in `app/Http/Controllers/Market/CooperativeController.php` by removing the redundant `createCooperative` definition.
- [ ] Modify `app/Http/Controllers/Support/VueRouteMap.php` to ensure `toCamelCase` returns PascalCase for better Vue component matching.
- [ ] Update `app/Http/Controllers/Helpers/Permissions/UserPermissions.php` to grant `ViewOrganizations` permission to the `OPERATOR` role.
- [ ] Improve error handling in `resources/js/Pages/CooperativeList.vue` to display a message when data loading fails.
- [ ] Execute `npm run build` and restart the `bukidbountyapp` Docker container to apply changes.

View File

@@ -0,0 +1,61 @@
# TODO: Fix URL-based Page Navigation (useNavigate URL Format Issue)
## Problem Summary
Pages return "page not found" when accessed via URL. The issue is a mismatch between how `useNavigate.js` generates URLs and how `VueRouteMap.php` parses them back to component names.
### Root Cause
- **Frontend** (`useNavigate.js`): Converts `CooperativeList``/cooperativelist` (no separators)
- **Backend** (`VueRouteMap.php`): Expects hyphenated format like `/cooperative-list` to properly convert back to `CooperativeList`
- **Result**: Component name mismatch → page not found
---
## Fix Steps
### Step 1: Fix Frontend URL Generation
**File**: `resources/js/composables/Core/useNavigate.js`
- [ ] Modify the URL generation logic to convert camelCase/PascalCase page names to kebab-case
- [ ] Change: `page.replace(/\./g, '/').toLowerCase()`
- [ ] To: Convert `CooperativeList``cooperative-list` (hyphenated kebab-case)
- [ ] Ensure all page navigations generate proper kebab-case URLs
### Step 2: Verify Backend Route Parsing
**File**: `app/Http/Controllers/Support/VueRouteMap.php`
- [ ] Verify `toCamelCase()` method properly converts kebab-case back to PascalCase
- [ ] Confirm: `cooperative-list``CooperativeList`
- [ ] Test with multi-word component names
### Step 3: Test URL Patterns
- [ ] Test direct URL access: `/cooperative-list` → loads `CooperativeList` component
- [ ] Test direct URL access: `/buy-view-product-market` → loads `BuyViewProductMarket` component
- [ ] Test URLs with hash parameters: `/edit-user--hHASHKEY`
- [ ] Test URLs with payload parameters: `/some-page--e:ENCODED_PAYLOAD`
### Step 4: Verify Browser Back/Forward Navigation
- [ ] Test popstate event handling with new URL format
- [ ] Ensure browser history works correctly with kebab-case URLs
### Step 5: Check Existing Routes
- [ ] Verify all routes in `VueRouteMap::$routes` array work with new URL format
- [ ] Ensure no broken links or navigation issues
---
## Expected Outcome
- All pages accessible via direct URL
- Browser refresh maintains current page
- Browser back/forward navigation works correctly
- URLs are clean and SEO-friendly (kebab-case)
## Files to Modify
1. `resources/js/composables/Core/useNavigate.js` - Fix URL generation
2. Potentially `resources/js/composables/useUrlEncoder.js` - If hash/payload encoding needs adjustment
## Testing Checklist
- [ ] Navigate to a page, copy URL, open in new tab → page loads correctly
- [ ] Refresh page while on a route → page stays on same route
- [ ] Browser back button works correctly
- [ ] URLs with hashkey parameters work
- [ ] URLs with payload parameters work

View File

@@ -0,0 +1,5 @@
- [ ] Add loading state guard to `startNewSession` and `completeTransaction` actions in `resources/js/stores/pos.js` to prevent race conditions.
- [ ] Update the "Confirm & Pay" button in `resources/js/Pages/PosMain.vue` to be disabled when `posStore.loading` is true and add a loading spinner.
- [ ] Modify the `getPosSessions` method in `app/Http/Controllers/Market/PosController.php` to exclude sessions with status `active` from the history records.
- [ ] Test the checkout flow to ensure that double-clicking the completion button does not trigger multiple new session initializations.
- [ ] Verify that the "empty transaction" is no longer visible in the POS History table upon completion of a sale.

View File

@@ -0,0 +1,83 @@
# TODO: Allow Users to Join Cooperatives via User Settings
## Overview
Implement functionality to allow users to join cooperatives stored in the user's `settings` JSON field, with a dedicated composable and controller for managing user additional details and enabling quick search of users by cooperative membership.
---
## Step 1: Database Schema Update
- [ ] Verify that `settings` field in `users` table is already JSON type (already exists and cast as array)
- [ ] No migration needed - `settings` field already supports array data
---
## Step 2: Create UserAdditionalDetailsController
- [ ] Create `app/Http/Controllers/UserManagement/UserAdditionalDetailsController.php`
- [ ] Implement `getDetails()` method - Retrieve user's additional details including cooperatives
- [ ] Implement `updateCooperatives()` method - Add/remove cooperatives from user settings
- [ ] Implement `searchUsersByCooperative()` method - Quick search for users by cooperative hashkey
- [ ] Implement `getUserCooperatives()` method - Get all cooperatives a user has joined
- [ ] Use ResponseHelper for consistent API responses
- [ ] Add proper authorization checks using UserPermissions
---
## Step 3: Create useUserAdditionalDetails Composable
- [ ] Create `resources/js/composables/useUserAdditionalDetails.js`
- [ ] Implement `fetchUserDetails()` - Fetch current user's additional details
- [ ] Implement `joinCooperative(cooperativeHash)` - Add cooperative to user's settings
- [ ] Implement `leaveCooperative(cooperativeHash)` - Remove cooperative from user's settings
- [ ] Implement `getJoinedCooperatives()` - Get list of cooperatives user has joined
- [ ] Implement `searchUsersByCooperative(cooperativeHash)` - Search users by cooperative
- [ ] Add loading states and error handling
- [ ] Follow existing composable patterns (useUserSettings.js as reference)
---
## Step 4: Register Routes
- [ ] Add routes in `routes/api.php` or appropriate route file:
- `POST /UserAdditionalDetails/Get` - Get user details
- `POST /UserAdditionalDetails/UpdateCooperatives` - Update cooperatives in settings
- `POST /UserAdditionalDetails/SearchByCooperative` - Search users by cooperative
- `POST /UserAdditionalDetails/GetCooperatives` - Get user's cooperatives
---
## Step 5: Update User Model (if needed)
- [ ] Verify `settings` field is in `$fillable` array (already present)
- [ ] Verify `settings` field is cast as `array` (already present)
- [ ] Add helper method `getCooperativesAttribute()` to easily access cooperatives from settings
- [ ] Add helper method `hasJoinedCooperative($cooperativeHash)` to check membership
---
## Step 6: Integration with Existing Cooperative System
- [ ] Ensure compatibility with existing `CooperativeController::joinCooperative()`
- [ ] When user joins via CooperativeController, also update user settings
- [ ] Maintain consistency between `cooperative_members` table and user settings
---
## Step 7: Testing & Validation
- [ ] Test joining a cooperative updates user settings correctly
- [ ] Test leaving a cooperative removes from user settings
- [ ] Test quick search returns correct users by cooperative
- [ ] Test authorization and permission checks
- [ ] Test edge cases (duplicate joins, non-existent cooperatives)
---
## Files to Create/Modify
1. **NEW**: `app/Http/Controllers/UserManagement/UserAdditionalDetailsController.php`
2. **NEW**: `resources/js/composables/useUserAdditionalDetails.js`
3. **MODIFY**: `routes/api.php` (or appropriate route file)
4. **MODIFY**: `app/Models/User.php` (add helper methods)
5. **MODIFY**: `app/Http/Controllers/Market/CooperativeController.php` (sync with settings)
---
## Technical Notes
- Cooperatives will be stored in `settings.cooperatives` as an array of hashkeys
- Example structure: `settings: { cooperatives: ['hash1', 'hash2', ...] }`
- Quick search should use JSON query to find users with specific cooperative in settings
- Follow existing patterns from `useUserSettings.js` and `CooperativeController.php`

View File

@@ -0,0 +1,8 @@
- [ ] Reduce the "Enroll New Farmer" button size and remove `w-100` in `CooperativeDetail.vue`.
- [ ] Import `SearchableTableWrapper` component in `CooperativeDetail.vue`.
- [ ] Add reactive variables for `searchQuery` and `tableDensity`.
- [ ] Implement a computed property `filteredMembers` to filter `cooperative.members` based on `searchQuery`.
- [ ] Replace the grid layout for members with `SearchableTableWrapper`.
- [ ] Define the member table structure (Full Name, Role, Actions/Chevron) inside the `table` slot of `SearchableTableWrapper`.
- [ ] Apply the density styles/classes to the table rows/cells.
- [ ] Ensure the "View Member" functionality is preserved for table rows.