83 KiB
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
trueby default, used for soft-deactivation or status tracking. - hashkey: string (300), unique. A unique identifier for every record, typically generated by
App\Listeners\ModelSavingListenerusingStr::uuid() . Str::random(100).
Audit Fields & Eloquent Events
- Audit Fields:
created_byandupdated_byare critical for audit trails across all modules. - ModelSavingListener:
app/Listeners/ModelSavingListener.phpautomatically populateshashkey,created_by, andupdated_byfor Eloquent models during theSavingevent. - Raw DB Bypass: Using
DB::table(...)->insert()orupdate()bypasses Eloquent events and listeners. - Manual Requirement: When using raw DB queries for performance (e.g., in
PosController.php), you MUST manually includecreated_byorupdated_by(usingAuth::id()) andupdated_at/created_at(usingnow()) in the data array to maintain data integrity and audit trails.
UI Standards
- Buttons and Inputs: Prefer
.rounded-pillfor a modern, premium look unless otherwise specified. - Form Spacing: Use
d-flex gap-2ormb-3to ensure interactive elements are not too close, especially on mobile views. - Form Controls: Standardize on
form-controlandform-selectwithrounded-pill. - Empty States: When a list or dataset is empty, provide a centered, clear message (e.g., "No records found") and an icon (using
fadorfaswithtext-mutedandopacity-2) to guide the user. - Avatars: For user profile pictures, if a custom photo is not provided (
photourl), usehttps://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 likeavatar.png. - Glassmorphism: Use
backdrop-filter: blur(15px);andbackground-color: rgba(var(--bg-card-rgb), 0.7);for floating headers and overlay components. A utility class.glass-cardis available inapp.jsfor 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) .selectorto ensure styles are correctly applied when thedark-modeclass is on thebody. - 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-lgto provide better target recognition. - Dashboard Stat Cards:
fa-2xto 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-4xfor a bold, centered focus on primary consoles. - Bulk Tool Icons:
fa-2xto accentuate the tool's category and provide clear differentiation. - Table Actions:
fa-lgfor critical row-level controls (Download, Delete, Edit).
- Navigation Icons:
- Dark Mode Scoped Fixes: Use
:global(.dark-mode) .selectoror theme variables (e.g.,var(--bg-primary)) to override hardcoded colors in Vue<style scoped>blocks. Removetext-darkandbg-whiteclasses where theming is required. - Ultimate Console Premium Header: The administrative header uses a deep, multi-stop radial gradient (
#020617base) 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-premiumfor 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-1instead ofbtn-groupin table action columns to prevent horizontal scrolling. - Text Labels: Use
.d-none .d-lg-inlinefor 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.
- Container: Use
- Sticky Headers: For configuration-heavy or high-density administrative pages (e.g.,
SystemSettings.vue,UltimateConsole.vue), usesticky-topon the primary header. This ensures critical actions like "Save" or "Back" remain accessible while scrolling through long forms. CRITICAL: Because the application uses a fixedTopHeader, you MUST provide an explicittopoffset (e.g.,top: 66px;ortop: var(--header-height, 66px);) and a highz-index(e.g.,1020) to ensure the sticky element sticks below the global navigation bar rather than being hidden behind it. Adjust sidebartopvalues accordingly (e.g.,13remto18remdepending 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
bodyand fixed elements (TopHeader,BottomNav) are centered usingmargin: 0 auto. - Full Width Mode: The
.is-full-widthclass on thebodyoverrides the max-width tonone, allowing components like the POS to utilize the entire screen. - Implementation: Managed globally in
resources/js/app.jsvia an injected<style>tag, and supported by:globaloverrides 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, orIlluminate\Support\Facades\Schemawill cause fatal class-not-found errors at runtime. - Correct imports for all migrations:
use Hyperf\Database\Schema\Blueprint; use Hypervel\Database\Migrations\Migration; use Hypervel\Support\Facades\Schema; - Why: Hypervel wraps Hyperf's database layer. The
Illuminatenamespace does not exist in this project. The HypervelMigrationclass provides proper coroutine-context-aware connection resolution viaApplicationContext. - 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
UserPermissionsandUserActions. - Ensure the permission is validated before granting access to the function.
- Add a corresponding permission in
Route file scope (important)
routes/api.phpis intentionally minimal. It only registers:IndexController@index,RemoteLogoutController@logout, andMarket\ActivityController(/activity/recent,/activity/search).- Products, users, stores, POS, cart, cooperatives, admin, settings, landing pages, etc. are ALL defined in
routes/web.php— notapi.php. Despite paths like/api/pos/...,/api/public/..., or/admin/users/listappearing 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.phpfirst. Do not assume/api/*paths live inroutes/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.
- Add a corresponding entry in
-
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:HASHKEYor/page-name--e:PAYLOAD - Product Context: When a product is accessed from a store context (e.g., in
BuyViewProductMarketorManageProductAdmin), use an encoded payload (--e:) containingproduct_hashkeyandstore_hashkeyto 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 fromposStore.activeSession.store.nameorposStore.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), orNEUTRAL(0). - ProductTransactionType: Defines specific transaction events (e.g.,
ONLINE_SALE,PURCHASE) which map to aTransactionFlow. - 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.vuevia/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_keyand capturedstoreHash) is preserved. A new session is then automatically started viaposStore.startNewSessionto ensure a seamless "customer-after-customer" checkout flow. To support this,pos_sessionsallows multiple records perpos_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
localStorageunderpos_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.phpmaps toVueRouteMap::handleSpa(). - Navigation: Uses
useNavigatecomposable. Pagenames should match Vue component names (PascalCase). - Initial State:
app.jsinitializes history state on load usingwindow.history.replaceStateto ensure back/forward navigation works for the entry page. - Prop Extraction: The backend automatically extracts hash/payload tokens and provides them as
target,hashkey, andidprops 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 byShipmentController.php. - Farmer Management:
FarmerProfileEdit.vue- For registering farmer profiles. Handled byFarmerController.php. - Verification Dashboard:
VerificationDashboard.vue- For admin review of farmer profiles. Handled byFarmerController.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_settingsundergroup_types. -
Key Fields:
hashkey,name,type,description. -
Membership: Linked via
group_memberspivot table withroleandmembership_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.
- Tables:
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.jsensures 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_AUTHorREDIS_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.phpandconfig/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 viaCachefacade. Supports SQL strings or Builder objects.set_cache($query, $data, $params = [], $ttl = 86400): Sets in cache viaCachefacade inside a fire-and-forget coroutine.erase_cache($query, $params = []): Deletes the specific cache key usingCache::forget.
- Implementation: Uses
Hypervel\Support\Facades\Cachefor better integration with the application's caching layer. - Async Logic:
set_cacheusesHyperf\Coroutine\Coroutine::createto 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 afteruserStore.fetchCurrentUser()inapp.js. - Purpose: Dynamically store and retrieve user-specific UI preferences in the
users.settingsJSON column. - Key Settings:
pos_layout:landscapeorregular. Controls the split-screen behavior inPosMain.vue.dark_mode: Boolean. Globally toggles the application's dark theme by applying the.dark-modeclass to thebody.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.settingsJSON column. Included in initial user data fetch viaAccountSettingsPageController@listDetails. - Implementation: Handled via state in
uiStoreand a global reactivewatchinapp.jswith{ immediate: true }. - UI Toggle: Located in
AccountSettings.vue, calls/UserSettings/Update. - Color Overrides: Global text color overrides for dark mode are defined in
app.jsand apply to standard typography tags (h1-h6,span,p,label,i) and genericdivelements, ensuring uniform color contrast across the application.
POS & Terminal Management (Advanced)
- "Start New Session" requires store context:
startNewSessionSilently(inusePosSession.js) guards against calling the API when bothstoreHashandaccess_keyare absent. When neither is available (e.g. navigating to/pos-mainwithout a URL hash or stored key), it setsposStore.errorand returns false instead of hitting/api/pos/start.PosController::startSessionnow returns 422 (not 404) when no store can be resolved, with a descriptive message.posStore.erroris surfaced in the cart area ofPosMain.vue. - Landscape Mode: Optimized for tablet/desktop usage with a split product/cart view.
- QR/Barcode Scanner: Integrated via
html5-qrcodeinPosMain.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 inpos_access_keys) and temporary session-specific random tokens. The frontend (pos.js) ensures only valid terminal keys are persisted tolocalStorage(viaPosMain.vue), and the backend (PosController.php) now allows authenticated users to start a session by specifying astore_hasheven if an invalid or unrecognizedaccess_keyis 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.vueandpos.jswhich 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). - Permission scoping: gated by
canUploadCategory($category)— allows anyone withUploadAllFiles(ULTIMATE / SUPER_OPERATOR). ForProductMarket, also allows roles withCreateProductForOwnStoreorAddProducttoOwnStoreviaProductPermissions::isActionAllowed(covers STORE_OWNER, STORE_MANAGER, OPERATOR). All other categories requireUploadAllFiles. - Frontend:
resources/js/composables/useFileUpload.jsposts to/File/Upload/${category}. TheCreateProductStoreOwnerwizard usescategory: '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::$RoleswithNoTargetUseris 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
ViewAccountingReportsandManageAccountingto 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.
- STORE_OWNER: Can manage
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_typeviaUserTypeService::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.
- Validation: All required fields (
- Table:
users - Controller:
app/Http/Controllers/UserManagement/CreateUserControllerUltimate.php - Page:
resources/js/Pages/CreateUser.vue - User List API:
/admin/users/listreturns 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
usersviauser_id.- Table:
user_infos - Edit Page:
resources/js/Pages/UserInfoEdit.vue
- Table:
- Navigation Fix (April 2026): Accessing the profile without a
targetprop 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 viacooperative_members. Data is stored in theorganizationstable.- 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
- Key Fields:
Home Store Owner Dashboard
- Component:
resources/js/Pages/Fragments/Home/HomeStoreOwner.vue- Used byHome.vueforSTORE_OWNERaccounts. - Balance Card Stats:
transactions_today_no(count) andcash_flow_today_php(signedSUM(amount * flow)for today, scoped to stores where the user is owner or instore_managers). Plusmy_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 toCreateStore.vuewith store-owner-only fields hidden), Import Products (BatchAddProducts), New Product (CreateProductStoreOwner— wizard-style flow, see below), My Products (ManageProductsAdmin— backendlistProducts_Adminalready 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 (replacesCreateProductUltimateon the Store Owner home). On mount, fetches/Admin/Stores/Selectable; if empty, opens ayesNoModaldirecting the user toCreateStoreand 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 editablepriceandavailable(defaults seeded from global). Submit: for "new" mode, firstPOST /Products/Admin/New/withTargetStore= first selected store (required because store owners lackCreateProductGlobal), then iterate every selected store andPOST /Products/AssignToStore/passingtarget,TargetStore,price,available,description.AssignProductToOwnStorenow upserts the pivot (updateExistingPivotif already attached, elseattach) and accepts an optionaldescriptionfield written to theprd_strpivot. - 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 + astore_managersrow. - 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_MANAGERexist. 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_managersfor the new store, in addition toowner_id/manager_id. - Name uniqueness:
stores.nameis validateduniqueglobally. 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).
- Hidden fields: Store Owner dropdown, Internal Remarks, Linked Cooperatives, Store Status. Owner is forced to the current user; status forced to
- User list filter (
POST /admin/user/list/numbers/hash): accepts an optionaltype(string or array ofacct_typevalues, e.g."store manager") to restrict the descendants-only result set. Response now includesacct_typeper row. - Route Map Adjustments:
/manage-stores,/batch-add-products, and/manage-productsnow allowstore owner(andstore managerfor/manage-storesand/manage-products) so the Store Owner home buttons land on accessible pages. /create-product-store-ownerroute: mapped inVueRouteMap.php→CreateProductStoreOwner, allowed forult,super operator,operator,store owner,store manager. Required so direct URL-bar access / page reload works (in-app navigation viauseNavigatepush-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 viaPOST /Products/GlobalList(ProductController@listGlobalProductsForPicker— returns allis_active=trueproducts regardless ofcreated_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-rowpriceandavailable, seeded from the global defaults, plus a "Bulk apply" panel that sets every row's price or availability at once. Submit iteratesPOST /Products/AssignToStore/per row (existing upsert endpoint — see L266). Entry points: (1)CreateStore.vueafterPOST /Store/Newsuccess — navigates here with the new store's hashkey instead ofListStores; (2)HomeStoreOwner.vueQuick Create branch afterPOST /Store/AutoCreate; (3)ManageStoresAdmin.vueper-row green+"Add Products" action (visible whencanModifyStore(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-dataendpoint.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 viaPOST /Products/Admin/FuzzySearch(ProductController@fuzzySearchByName, name-only LIKE + SOUNDEX +similar_textscoring, 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 asTargetStoreto/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 insideCreateProductUltimate.vueandBatchAddProducts.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 aWarningmodal is shown. Payload per leaf:source: 'new' | 'existing'. Forexisting, onlyproduct_hash,price,available,descriptionare sent; backend attaches the global product to the target store via theprd_strpivot, falling back to the global'sprice/descriptionwhen the leaf's value is blank or 0. Fornew, the global product is created and — when a target store is picked — also attached to that store with the same price/stock/description. Pivot tableprd_strsupportsavailable,price,description,is_activeetc. (seeStore::products()withPivot). -
Store-scoped product creation permission:
UserActions::CreateProductForOwnStore(granted to STORE_OWNER, STORE_MANAGER, OPERATOR, SUPER_OPERATOR).ProductController@createNew_Adminallows the request when the caller has eitherCreateProductGlobal(Big3) orCreateProductForOwnStore+ aTargetStorethey own/manage; the existing per-store ownership check at the top ofcreateNew_Adminenforces the latter. -
Selectable Stores Endpoint:
POST /Admin/Stores/Selectable→StoreController@listSelectableStoresForAddingProduct. Non-Big3 query was widened from descendants-only to include self + descendants +store_managerspivot, so a fresh store owner sees their own store(s). -
Manage Listings Modal (
ManageProductsAdmin.vuestore icon): opens a per-product modal listing the user's selectable stores. For each store it shows a "Listed" / "Not listed" badge based onPOST /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 callsPOST /Products/UnassignFromStore/→ProductController@RemoveProductFromStore.RemoveProductFromStorewas relaxed from the Big3-onlyRemoveProductfromAnyStorepermission to ownership-aware checks mirroringAssignProductToOwnStore(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
ultimatemiddleware andUserActions::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.
- Raw SQL Query execution (
-
Backend:
app/Http/Controllers/Market/UltimateController.php -
Migrate Endpoint:
POST /admin/ultimate/migrate→UltimateController@runMigrate. Runsphp artisan migrate --force(non-interactive, prod-safe). Auth:auth+ultimatemiddleware. Returns{ success, output }. -
Flush Endpoint:
POST /admin/ultimate/flush→UltimateController@flushData. Body{ target }. Valid targets:transactions,pos_sessions,stores(truncatesstr+prd_str+store_managers),products(truncatesprd_items+prd_str),cooperatives(truncatescooperative_*child tables + deletesorganizationswheretype='COOPERATIVE'),carts(truncatescart_items+carts),farmer_profiles,cache(Redis flushDB). UI lives in the "Dangerous Zone" tab viaflushOptionsinUltimateConsole.vue. -
Composable:
resources/js/composables/useUltimate.js- Contains methods likegetStats(to retrieve system metrics),toggleMaintenance,sendGlobalMessage, andrunCommand(for Artisan calls). Ensure methods are called exactly as defined (e.g.,getStatsinstead offetchStats).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, andDESCRIBEfor data retrieval; others use standard DB statements. Artisan commands are whitelisted. -
Dark Mode Overlays: Global overlays such as the
.loading-overlayinapplication-layout.blade.phpmust be dark-mode aware. Ensurebackground: 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-whiteorbg-lightclasses directly in Vue templates for components that should persist in dark mode (like modals inPosMain.vue). Use themed CSS variables or:global(.dark-mode)overrides. -
listProductsData store resolution: Must use sequential
if (!$targetStore)guards, NOTif/elseif. A staleaccess_keyin localStorage (e.g., a revoked key or session-specific token) must not block thestore_hash/session_hashfallbacks. Priority order: access_key → session_hash → store_hash. Bug pattern: usingelseifcauses 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 inPosMain.vueand related components to ensure stable terminal identity and prevent logic fragmentation. -
Session Hashkey Integrity: The backend
PosController.phpmust NOT truncate hashkeys at 36 characters. Thehashkeyfield 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
addItemandremoveItemendpoints 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 secondloadSessioncall, 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
PosSessionArchivelogic 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 inuseSessionGuard.js. - Security: Requires
webandauthmiddleware 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
.sqlfiles, compressed to.7zUltra, and then stored as base64 in thefile_contenttable. Thedb_backupstable tracks these records. Temporary.sqland.7zfiles are deleted from the filesystem immediately after being saved to the database to prevent persistence on disk. - Exclusion: The
db_backupstable is excluded from the SQL dump to prevent recursive growth. - UI: Managed in
resources/js/Pages/UltimateConsole.vueunder 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 inVueRouteMap.php). - Logic: Uses
PosHistoryListcomponent andposStorefor 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, andstore 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, andprimary_color. - Dynamic Branding (Frontend):
- Pattern: Use
uiStore.appNamein Vue templates instead of hardcoded strings like "BukidBounty". - Components:
Home.vue,Login.vue,AccountSettings.vue, andHomePublic.vueuse this pattern. - Tab Title: Automatically managed via
uiStore.pageTitle, which defaults toappName.
- Pattern: Use
- Inertia Shared Data:
- Controller:
App\Http\Controllers\Support\Inertia.php- Automatically includessystemSettingsin everyInertia::rendercall. - Synchronization:
app.jspopulates theuiStorefrominitialPage.props.systemSettingsimmediately upon mounting. This eliminates the "flash" of default branding before the API background refresh completes.
- Controller:
Common Modals & UI Components
- BaseModal:
resources/js/Components/Core/BaseModal.vue- Standardized modal structure with header, body, and footer slots. Supportsv-modelfor visibility. - ConfirmModal:
resources/js/Components/Core/ConfirmModal.vue- Specialized modal for action confirmations (Maintenance toggle, Backups, Flushes). Supportsdanger,warning, andinfovariants. - UltimateQueryModal:
resources/js/Components/Ultimate/UltimateQueryModal.vue- Advanced SQL console for ULTIMATE accounts, refactored to useBaseModaland theme-aware styling. - FileImage:
resources/js/Components/Core/FileImage.vue— emits a single<img>with@errorfallback. The:srcprop is forwarded as-is, so callers must pass a string URL.photourlfrom product endpoints may bestring | string[] | null; passingphotourl[0]whenphotourlis a string yields a 1-char URL that 404s. Use a safe accessor likeconst firstPhoto = (v) => Array.isArray(v) ? (v[0] || '') : (v || '');before binding. - FileList::resolvedUrl():
app/Models/FileList.php(tablefile_list) returnscdn_urlif non-empty, else/RequestData/File/{hashkey}.str.photourlis a JSON array offile_listhashkeys — raw hashkeys are NOT renderable URLs.StoreController@viewStoreDetailsresolves them into$store->resolved_photos(array of usable URLs, cdn_url preferred);ViewStoreMarket.vuereadsstore.resolved_photosfor its avatar/banner/photo-gallery modal. - CardSimple:
resources/js/Components/Core/CardSimple.vue— wrapper withis-premiumdefaulting totrue, which adds a translucent background and atranslateY(-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') inapp/Enums/UserTypes.php. Home fragments:resources/js/Pages/Fragments/Home/HomeCoopOfficer.vue,HomeCoopMember.vue(wired inHome.vue). - Controller:
app/Http/Controllers/Support/ChapterController.php— methodsgetOrgChart,getOfficerScope,memberSearch,assignOfficer,createChapter,publicGetChapter,publicRegisterToChapter. - Routes (
routes/web.php): authedPOST /Chapters/OrgChart,/Chapters/Officer/Scope,/Chapters/Members/Search,/Chapters/Officer/Assign,/Chapters/Create; publicGET /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 base64payloadprop{coop_hash,chapter_hash}). - CHILD_LEVELS: national→region→province→[city|municipal]→barangay.
chapters.levelenum has NO 'municipal' (peers in code only). Level checks usein_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']+ statschapter_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}; publicPOST /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 insystem_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@getPublicSettingsexposes the list to the frontend, andSystemSettingsController@updatehandles saving toggled states. - Sync Mechanism:
- Initial Load:
app.jscallsuiStore.refreshSettings()on startup. - Background Refresh:
app.jsruns a background interval callinguiStore.refreshSettings()every 1 minute to stay in sync. - Pre-navigation Refresh:
useNavigate.jsautomatically 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 triggeruiStore.refreshSettings()orsyncSettings()after updatingdisabled_pagesto ensure immediate session-wide consistency. - Case-Insensitivity: All disabled page checks are case-insensitive to ensure reliable blocking regardless of component naming conventions.
- Initial Load:
- Affected Files:
resources/js/composables/Core/useNavigate.js- Global navigation guard.resources/js/stores/ui.js- Reactive store fordisabledPagesandlastSyncedtimestamp.app/Http/Controllers/Support/VueRouteMap.php- Backend enforcement for direct URL access; implementsisUserAllowedlogic usingallowedUserTypeskey.app/Http/Controllers/Helpers/Permissions/UserPermissions.php- Core RBAC engine mapping roles toUserActionsenums.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.vueand other navigation menus, "Global System Settings" must point to theSystemSettingspagename, while "My Personal Profile" or "Account" points toAccountSettings.
User Additional Details
- Settings Field: JSON column in
userstable. - Cooperatives: Stored in
settings.cooperativesas 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::joinCooperativeto maintain consistency betweencooperative_memberstable and user settings.
SSE & Real-time Integration
- Controller:
app/Http/Controllers/Support/SSEController.php - Mechanism: Uses
Hypervel\Coroutine\Parallelfor 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) asinventory_deltas. - Inventory: Product inventory updates (stock/price changes) are pushed as deltas based on the
lastSynctimestamp across all stores owned/managed by the user.
- 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
- Frontend Integration:
- Composable:
resources/js/composables/Core/useSessionGuard.jsacts as the primary consumer and dispatches data to Pinia stores. - Stores:
pos.js(syncFromSSE),product.js(syncFromSSE), andui.js(syncDisabledPages) handle the reactive merge of incoming data into the UI state. - Caching: The
product.jsstore maintains adetailsCache(Object/Map) of full product details to enable instant page transitions in the Marketplace and Product Detail views.
- Composable:
Session Management & PWA
- Multi-tab Logout: Synchronized via both SSE (
isloggedin: falsepayload) and alocalStoragestorageevent (logout_eventkey). When any tab logs out, it clearslocalStorageand setslogout_event, which triggers an immediate redirect to/loginin all other open tabs via theuseSessionGuardcomposable. - SSE Connection: Handled in
resources/js/composables/Core/useSessionGuard.js. It maintains a persistentEventSourceconnection to/sse/streamfor real-time updates and session monitoring. The backendSSEControllerexplicitly sends anisloggedin: falsemessage 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 inLogin.vuewas 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 inSSEControllerto release the lock immediately.
- 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
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, format09XXXXXXXXX.username: Unique, required for all users.name: Required (Display Name).password: Min 6 characters.type: User Type (viaUserTypesenum).
- 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. ThelistAllUsersforParentSelectHTMLmethod 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
npmis 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"
- Primary:
- 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
mainbranch is periodically updated to match theexperimentalbranch to promote stable experimental features to production. This is done viagit checkout main && git reset --hard experimental.
RBAC & Session Hardening Best Practices
- User Type Listing (Dropdown Fix): The
CreateUserControllerUltimate@listAllUserTypesforSelectHTMLmethod must handleUserTypesenums directly. Avoid usingUserTypes::from($currentUser->acct_type)if theacct_typeattribute is already cast to the enum in theUsermodel, as this will trigger aTypeError. - SPA Route Protection: All sensitive management pages (e.g.,
/create-user) MUST be explicitly defined inApp\Http\Controllers\Support\VueRouteMap::$routeswith anallowedUserTypesarray. If a route is not mapped, it defaults to allowing any authenticated user via thehandleSpacatch-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.vuecomponent must executesessionStorage.clear()within itsonMountedhook. 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 implementcomputedfiltering for action buttons based onUserTypesto 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 topriority_sectorsin 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 theaddressesJSON 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_numberinUserInfoController@updateUserInfo. - Virtual Attributes:
age(calculated on the fly fromdob).
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/registercreates a new USER account and registers them as a MEMBER. - Controller Methods:
CooperativeController@publicGetCooperativeandCooperativeController@publicRegisterMember. - Parent Assignment: Uses the cooperative's
created_byuser 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.vuepage (route:/cooperative-member-register--h:COOP_HASH). - Registration Logic: Handled by
CooperativeController@registerMember. It automatically links the user to the organization and updates thesettings.cooperativesarray in theuserstable 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.vuepage 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, andOPERATORaccount 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.) inManageProductAdmin.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_managerstable 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 inCreateStore.vueandEditStoreUltimate.vue, allowing multiple users to be assigned as managers for a single store. - Context-Aware Product Locking: A UI pattern in
ManageProductAdmin.vuethat 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:
ULTIMATEusers 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
PosControllermethods:startSession,getSession,getPosSessions,getTodayStats, andgetCustomers. - Ancillary Helper:
UserPermissions::isAncestorOf($ancestor, $descendant)- Recursively checks user hierarchy viaparentuid.
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 tolocalhost:9522. - Port:
9522(Host) maps to9501(Container) for thebukidappservice. - Usage:
- Temporary URL:
./scripts/tunnel.sh(generates a*.trycloudflare.comlink). - Persistent Tunnel:
./scripts/tunnel.sh <YOUR_TUNNEL_TOKEN>.
- Temporary URL:
- Implementation: Uses Docker
cloudflare/cloudflaredwith--network hostto 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 viadocker service ls. - Compose services (like DragonflyDB): Use the full container name as hostname (e.g.,
prodservers-dragonflydb-rnfaje-dragonflydb-1). Find viadocker ps --format '{{.Names}}'.
- Swarm services (Dokploy-managed databases like MySQL): Use the service name as hostname (e.g.,
- Common Error — Redis DNS Lookup Failed: Caused by
REDIS_HOSTpointing to a container name that is not on the same Docker network. Fix by addingdokploy-networkto the DragonflyDB compose and using the correct container name. - Port Exposure: Do NOT use host port mapping (e.g.,
9522:9501) indocker-compose.ymlfor 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_DATABASEnames andREDIS_DBnumbers (0–15).
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=trueat a time, enforced at the application level viaLandingPage::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.vuefetches the active landing page via/api/public/landing-pageand 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, andCOORDINATORroles. - RBAC:
UserActions::ManageLandingPagespermission added to$RoleswithNoTargetUser. - Route Map: Registered as
LandingPageEditorinVueRouteMap.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-3aon GitHub, served via jsDelivr. - Pinned Tag: Configured in
config/cdn.php(base) andpublic/sw.js(CDN_SHA). Current:v2026.05.14-vendor(SHA927de981da85). - Asset Path Convention:
a/<sha256-12char>.<ext>. Extension is.binby 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 requiresimage/svg+xml—<img src="*.bin">containing SVG markup silently fails to render. Therefore SVGs MUST be stored asa/<hash>.svgso jsDelivr serves them with the correct MIME. - Pipeline Skill:
cdn-asset-pipeline(in~/.claude/skills/) handles sync. After updating extension whitelist, all SVG.binfiles must be re-synced to.svgand orphaned.binfiles removed. - CSS Relative URLs Break on CDN: CSS files served from CDN use the flat
a/<hash>.<ext>path, so any relativeurl(...)inside them (e.g.@font-face src: url('icomoon.ttf')) resolves toa/icomoon.ttfand 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→ CDNa/c99ad0580805.bin. Reference absolutely inicons-alipay.cssso font loads regardless of where the CSS is hosted. - Icomoon Font Broken on jsDelivr (May 2026): The
.ttfis published asa/c99ad0580805.binand served asapplication/octet-streamwithX-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 useicomoon.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/.otfto the CDN pipeline's preserve-extension whitelist and re-publish, or self-host the font under/publicso 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 * 60seconds) with a maximum of 1000 entries. - Implementation: Configured in
public/sw.jsvia WorkboxregisterRoute.
Infrastructure & Docker
-
Main Service:
bukidapp- PHP Swoole/Hypervel application service.- Container Name: Usually
bukidbountyapp-bukidapp-1(orbukidbountyapp-nginx-1for the entry point).
- Container Name: Usually
-
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
/publicdirectly (CSS, JS, images) to minimize latency and bypass the PHP/Swoole stack for static requests. -
Docker Compose: Managed via
docker-compose-local.yml(development) anddocker-compose.yml(production). -
Service Dependency: The
nginxservice depends onbukidapp. -
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 namebukidapp. Nginx must NOT resolvebukidappdirectly on this shared network. Instead, use a unique network alias (swoole-upstream) on theapp-internalnetwork. - Upstream Alias: The
bukidappservice declaresaliases: [swoole-upstream]onapp-internal, anddocker/nginx/default.conftargetsserver swoole-upstream:9501.
- Uses
-
Priority Sectors: Stored as a JSON array in
system_settingsunder the keypriority_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_settingsunder the keygroup_types. Defines the categories of organizations (e.g., COOPERATIVE, ASSOCIATION). -
Address Fields: Stored as a JSON array in
system_settingsunder the keydefault_address_fields. Defines the expected keys in theuser_infos.addressesJSON (e.g.,house_no,street,zipcode). -
JSON Setting Editor: A dynamic editor in
SystemSettings.vuethat 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(notbukidapp), so Traefik routesdomain → nginx:80 → bukidapp:9501. - Traefik uses Docker labels (not DNS) to find the target container, so the nginx service MUST be on
dokploy-networkfor Traefik to reach it.
-
Local vs Production Volumes:
- Local (
docker-compose-local.yml): Uses bind mount.:/var/appfor live code reloading during development. Nginx mounts./public:/var/app/publicdirectly. - Production (
docker-compose.yml): Does NOT use bind mounts onbukidapp. The image is self-contained —composer installandnpm installrun duringdocker build, and the resultingvendor/andnode_modules/are baked into the image. - Shared Build Volume (Production): A named volume
public_buildshares Vite-built assets from bukidapp to nginx. bukidapp copies/var/app/public/to/var/app/public-shared/(the shared volume) on startup via the commandsh -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
./publicdirectory (from the repo clone) does NOT contain Vite build output — those are generated inside the bukidapp container duringdocker build. A bind mount would hide the built assets.
- Local (
-
Dockerignore:
.dockerignoreexcludesvendor/,node_modules/,.git, and.env*to prevent host directories from overwriting dependencies installed during image build viaCOPY . ..
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 associatedMODULE_<NAME>_ENABLEDenvironment variables. - Master Toggle:
MODULES_SYSTEM_ENABLED- If set tofalse, all module-specific checks are bypassed (everything is enabled). Defaults totrue. - Helper:
App\Support\ModuleHelper::isEnabled($moduleKey)- Used to check module status in code. - Middleware:
module:<key>(e.g.,module:pos) - Applied to routes inweb.phpto 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 (handleSpaandregisterRoutes) automatically blocks access and redirects to/if the module is disabled. - Frontend Sync: Module states are exposed via
SystemSettingsController@getPublicSettingsunder themodule_stateskey. 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_settingsunder keyqrph_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—ultimatemiddleware; saves QRPH code to system_settings.POST /Financial/Qrph/Decode—ultimatemiddleware; 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 viaqrcodenpm library — no external API). - Amount injection: In
MyWallet.vueQRPH 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.phprecognisescom.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.vuetop-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.vueshows a "Payment QR Code (QRPH)" card only for ULTIMATE accounts; drop-zone decodes QR image locally viaHtml5Qrcode.scanFile, uploads the image file, and shows Static/Dynamic badge.
Known Bug Patterns & Fixes
RBAC — Action Permission vs Page Access Mismatch
- VueRouteMap
allowedUserTypescontrols SPA page access;UserPermissions::roles()controls individual API action access. These must stay in sync. - Example:
OPERATORlisted in/list-propertiesallowedUserTypes, butViewProperties/ViewReferralswere missing fromUserTypes::OPERATOR->valueinroles()→ caused 403 on thePOST /admin/properties/listAPI. - Fix: always add the required
UserActions::*toroles()whenever a role is added toallowedUserTypesfor a page.
SPA Catch-all — Undefined $moduleKey
VueRouteMap::handleSpa()uses$moduleKeyon line ~530 but only assigns it inside theif/elsematch blocks. If the path matches no known route,$moduleKeyis undefined → potential 500.- Fix: initialize
$moduleKey = null;before theif ($routeSettings)block. /bukidbountyapppath is now registered in VueRouteMap as an alias ofHome(no login required) to prevent it hitting the catch-all fallback.
useUserNotes — Unauthenticated fetch
TopHeader.vuecallsfetchNotes()inonMountedunconditionally, firingGET /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 insideuseUserNotes::fetchNotes()itself as a safety net.
POS Access Key — STORE_OWNER / STORE_MANAGER missing create/delete/toggle permissions
/pos-access-keysVueRouteMap allowsstore ownerandstore manager, butroles()only gave themViewPosAccessKeys. Calls to create/delete/toggle keys returned 403.- Fix: add
CreatePosAccessKey,DeletePosAccessKey,TogglePosAccessKeyto bothSTORE_OWNERandSTORE_MANAGERinUserPermissions::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::viewProductDetailsaggregatessold_today(global) andstore_sold_today(per-store, when a store hash is provided) by summingquantityfiltered withwhereDate('created_at', today()). Surfaced inBuyViewProductMarket.vuePOS scan card alongside aprintPosCode()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— Cartscart_items— Cart Itemsstore_managers— Store ↔ Manager User pivot (multi-manager system)couriers— Couriersshipments— Shipmentspos_access_keys— POS Access Keyspos_sessions— POS Sessionspos_sessions_archive— Archived POS Sessionspos_transactions— POS Transactions
Organizations / Cooperatives
organizations— Organizations (cooperatives, associations, etc.)main_organizations— Top-level / parent organization recordsorg_str— Organization↔Store pivot (cooperative-store linkage)cooperative_members— Cooperative Memberscooperative_documents— Cooperative Documentscooperative_resolutions— Cooperative Resolutionscooperative_votes— Cooperative Voteschapters— Chapterschapter_members— Chapter Membersgroups— Groupsgroup_members— Group Membersfarmer_profiles— Farmer Profiles
Users / Auth / Accounting
users— Usersuser_infos— User Info (addresses JSON, profile extras)personal_access_tokens— API personal access tokensaccounts— Accounting accountsaccount_transactions— Account Transactionsmember_ledgers— Member Ledgersglobal_transactions— Global Transactions (cross-domain ledger)
Properties / Referrals
properties— Propertiesreferrals— Referralsreferral_keys— Referral Keys
Files / System
file_list— File List (uploaded file index)file_content— File Content (binary/blob storage)landing_pages— Landing Pagesannouncements— Announcementssystem_settings— System Settings (key/value, JSON values supported)db_backups— Database Backup recordslogs— Generic Logstable_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 anULTIMATEsystem user; pick a specific actor via envPERF_ACTOR_HASH). - Routes (declared in
routes/api.php, true API routes — NOT inweb.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 rawpos_transactionsinsert → optional complete). Reportsavg/min/max cycle_msplus per-cycleopen_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>orexists:<table>,<col>rules, look up the model'sprotected ?string $tablevalue rather than pluralizing the model class name.