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

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.