From fbb7e3ff372c4b8ee3a7e9d7e1ee0c13ac83079f Mon Sep 17 00:00:00 2001 From: Jonathan Sykes Date: Sun, 7 Jun 2026 03:09:09 +0800 Subject: [PATCH] feat: implement barangay system phases 2-14 Complete adaptation from BukidBountyApp to Philippine barangay governance: - Barangay models: Resident, Household, HouseholdMember, Blotter, BlotterHearing, DocumentRequest, RequestPayment, RequestType, BarangayProject, BarangayBudget - Controllers: ResidentController, HouseholdController, BlotterController, BlotterHearingController, DocumentRequestController, RequestTypeController, ProjectController, BudgetController, QRPHController, AdminConsoleController, UserController, FileController, ChapterController, LoginController - Vue pages: Home, ManageResidents, ResidentProfile, ManageHouseholds, ManageBlotters, BlotterDetail, RequestDocument, ManageDocumentRequests, DocumentRequestDetail, ManageRequestTypes, ManageProjects, BudgetLedger, AdminConsole - Barangay roles: PunongBarangay, Kagawad, Secretary, Treasurer, SK, Tanod, BHW, Staff, Resident - UserPermissions matrix rewritten with barangay-specific permission mappings - VueRouteMap replaced with barangay SPA routes - UserActions enum references corrected across all controllers - Removed all market/cooperative/POS/subscription code and models --- .../Commands/ImportReadmeMemberData.php | 78 - app/Console/Commands/SeedDemoProducts.php | 90 - .../Accounting/AccountingController.php | 751 -------- .../Admin/AdminConsoleController.php | 162 ++ .../Admin/PosAccessKeyController.php | 174 -- .../Admin/SystemSettingsController.php | 33 - app/Http/Controllers/Admin/UserController.php | 207 +++ app/Http/Controllers/Auth/LoginController.php | 184 ++ .../Barangay/BlotterController.php | 140 ++ .../Barangay/BlotterHearingController.php | 77 + .../Controllers/Barangay/BudgetController.php | 126 ++ .../Barangay/DocumentRequestController.php | 210 +++ .../Barangay/HouseholdController.php | 164 ++ .../Barangay/ProjectController.php | 135 ++ .../Barangay/RequestTypeController.php | 95 + .../Barangay/ResidentController.php | 159 ++ app/Http/Controllers/ChapterController.php | 241 +++ app/Http/Controllers/FileController.php | 223 +++ app/Http/Controllers/FilesMainController.php | 412 ----- .../GlobalTransactionController.php | 177 -- .../Controllers/Helpers/Legacy/LibLegacy.php | 2 - .../Permissions/ProductPermissions.php | 191 -- .../Helpers/Permissions/UserPermissions.php | 946 +++------- .../Controllers/Market/ActivityController.php | 55 - .../Controllers/Market/BatchController.php | 577 ------ .../Controllers/Market/CartController.php | 142 -- .../Market/CooperativeController.php | 470 ----- .../Market/CooperativeDocumentController.php | 257 --- .../Controllers/Market/CreditController.php | 269 --- .../Controllers/Market/FarmerController.php | 143 -- .../Market/GovernanceController.php | 178 -- .../Market/PerformanceController.php | 472 ----- app/Http/Controllers/Market/PosController.php | 774 -------- .../Controllers/Market/ProductController.php | 1140 ------------ .../Market/ProductPhotoSearchController.php | 190 -- .../Controllers/Market/ShipmentController.php | 163 -- .../Controllers/Market/StoreController.php | 1510 --------------- .../Controllers/Market/UltimateController.php | 565 ------ .../Controllers/Market/UserInfoController.php | 169 -- .../Market/UserSettingsController.php | 52 - app/Http/Controllers/PageMemoryController.php | 101 - .../Pages/AccountSettingsPageController.php | 219 --- .../Pages/Core/ApplicationController.php | 34 - .../Controllers/Pages/Core/HomeController.php | 28 - app/Http/Controllers/Pages/PageController.php | 20 - .../Pages/TransferMyCreditPageController.php | 84 - .../Pages/UserListPageController.php | 89 - .../Pages/UserModifyAdminPageController.php | 1031 ----------- .../Controllers/Payment/QRPHController.php | 99 + app/Http/Controllers/Photos/PhotoGallery.php | 60 - .../Property/PropertyManagementController.php | 41 - .../Controllers/PwaManifestController.php | 2 +- .../Subscription/SubscriptionController.php | 201 -- .../SubscriptionPlanController.php | 126 -- .../Controllers/Support/ChapterController.php | 21 +- .../Controllers/Support/SSEController.php | 4 - app/Http/Controllers/Support/VueRouteMap.php | 394 +--- app/Http/Controllers/UserCreateController.php | 78 - .../CreateUserControllerUltimate.php | 272 --- .../UserAdditionalDetailsController.php | 129 -- .../UserPages/UltimateUserController.php | 14 - app/Http/Controllers/viewHelperController.php | 4 +- app/Http/Middleware/CheckMaintenanceMode.php | 2 +- app/Http/Middleware/EnsureUserIsUltimate.php | 2 +- app/Models/Accounting/Account.php | 57 - app/Models/Accounting/AccountTransaction.php | 49 - app/Models/Accounting/MemberLedger.php | 55 - app/Models/Barangay/BarangayBudget.php | 42 + app/Models/Barangay/BarangayProject.php | 44 + app/Models/Barangay/Blotter.php | 70 + app/Models/Barangay/BlotterHearing.php | 32 + app/Models/Barangay/DocumentRequest.php | 78 + app/Models/Barangay/Household.php | 51 + app/Models/Barangay/HouseholdMember.php | 30 + app/Models/Barangay/RequestPayment.php | 32 + app/Models/Barangay/RequestType.php | 34 + app/Models/Barangay/Resident.php | 67 + app/Models/Chapter.php | 63 +- app/Models/Generic/TableLog.php | 47 - app/Models/GlobalTransaction.php | 72 - app/Models/Market/Cart.php | 35 - app/Models/Market/CartItem.php | 39 - app/Models/Market/CooperativeDocument.php | 41 - app/Models/Market/CooperativeMember.php | 63 - app/Models/Market/CooperativeResolution.php | 51 - app/Models/Market/CooperativeVote.php | 47 - app/Models/Market/Courier.php | 42 - app/Models/Market/Customer.php | 52 - app/Models/Market/FarmerProfile.php | 53 - app/Models/Market/MainOrganization.php | 44 - app/Models/Market/Organization.php | 78 - app/Models/Market/PosAccessKey.php | 89 - app/Models/Market/PosSession.php | 70 - app/Models/Market/PosSessionArchive.php | 49 - app/Models/Market/PosTransaction.php | 49 - app/Models/Market/Product.php | 91 - app/Models/Market/ProductTransaction.php | 102 -- .../Market/ProductTransactionSession.php | 84 - .../ProductTransactionSessionArchive.php | 72 - app/Models/Market/Shipment.php | 69 - app/Models/Market/Store.php | 117 -- app/Models/Market/StoreManager.php | 46 - app/Models/Market/UserInfo.php | 114 -- app/Models/Property/Property.php | 46 - app/Models/Property/Referral.php | 57 - app/Models/Property/ReferralKey.php | 41 - app/Models/Subscription/Subscription.php | 54 - .../Subscription/SubscriptionInvoice.php | 45 - app/Models/Subscription/SubscriptionPlan.php | 45 - app/Models/User.php | 23 +- app/Services/ActivityService.php | 2 - app/Support/AccountingTheme.php | 1 - app/Support/HashkeyResolver.php | 23 +- app/Support/SystemSettingsHelper.php | 10 +- ...13_create_market_products_stores_table.php | 240 --- ...0_06_155745_create_product_store_table.php | 44 - ...25_11_25_10949_create_accounting_table.php | 72 - ...00000_create_global_transactions_table.php | 46 - .../2026_03_23_000001_create_pos_tables.php | 76 - ...23_000002_create_pos_access_keys_table.php | 36 - ...03_add_expiry_to_pos_access_keys_table.php | 21 - ...23_000004_add_updated_by_to_pos_tables.php | 39 - ...dd_missing_columns_to_pos_transactions.php | 29 - ...ed_by_and_is_active_to_pos_access_keys.php | 33 - ..._add_flow_to_global_transactions_table.php | 27 - ...26_03_25_000003_create_customers_table.php | 29 - ...onstraint_from_pos_sessions_access_key.php | 30 - ...4500_create_property_management_tables.php | 89 - ...026_03_26_000001_create_couriers_table.php | 37 - ...26_03_26_000002_create_shipments_table.php | 51 - ...000003_create_farmer_management_tables.php | 60 - ...10001_create_cooperative_members_table.php | 42 - ...002_create_carts_and_cart_items_tables.php | 47 - ...erative_details_to_organizations_table.php | 49 - ...add_compound_index_to_pos_transactions.php | 25 - ...00002_expand_cooperative_members_table.php | 57 - ..._02_191649_create_store_managers_table.php | 37 - .../2026_04_17_000002_create_groups_table.php | 38 - ...4_17_000003_create_group_members_table.php | 41 - ...7_create_cooperative_resolutions_table.php | 37 - ..._194348_create_cooperative_votes_table.php | 36 - ...337_create_cooperative_documents_table.php | 33 - ...sioning_to_cooperative_documents_table.php | 29 - ..._18_043210_create_member_ledgers_table.php | 39 - ...xpand_cooperative_members_program_data.php | 57 - ..._sector_to_json_in_cooperative_members.php | 17 - ...2026_05_06_120000_create_org_str_table.php | 29 - ...120000_create_main_organizations_table.php | 32 - ..._08_120000_add_theme_stamp_to_accounts.php | 23 - ...08_120100_add_default_flow_to_accounts.php | 31 - ...2_000001_add_store_scope_to_accounting.php | 35 - ..._24_000001_create_subscriptions_tables.php | 98 - ...01_add_default_org_type_system_setting.php | 34 - ...000001_add_bible_verse_system_settings.php | 45 - ..._000001_add_cooperative_id_to_chapters.php | 25 - ..._28_000002_add_role_to_chapter_members.php | 22 - resources/js/Pages/AccountingDashboard.vue | 511 ------ resources/js/Pages/AddProductsToStore.vue | 466 ----- resources/js/Pages/AddTransaction.vue | 401 ---- resources/js/Pages/AdminConsole.vue | 194 ++ resources/js/Pages/AssignChapterOfficer.vue | 183 -- resources/js/Pages/AssignProductToStore.vue | 922 ---------- resources/js/Pages/Barangay/BlotterDetail.vue | 152 ++ resources/js/Pages/Barangay/BudgetLedger.vue | 178 ++ .../Pages/Barangay/DocumentRequestDetail.vue | 188 ++ .../js/Pages/Barangay/ManageBlotters.vue | 180 ++ .../Pages/Barangay/ManageDocumentRequests.vue | 113 ++ .../js/Pages/Barangay/ManageHouseholds.vue | 212 +++ .../js/Pages/Barangay/ManageProjects.vue | 193 ++ .../js/Pages/Barangay/ManageRequestTypes.vue | 116 ++ .../js/Pages/Barangay/ManageResidents.vue | 213 +++ .../js/Pages/Barangay/RequestDocument.vue | 137 ++ .../js/Pages/Barangay/ResidentProfile.vue | 238 +++ .../js/Pages/BatchAddCooperativeMembers.vue | 206 --- resources/js/Pages/BatchAddCooperatives.vue | 201 -- resources/js/Pages/BatchAddProducts.vue | 774 -------- resources/js/Pages/BatchAddStores.vue | 183 -- resources/js/Pages/BatchAddUsers.vue | 183 -- resources/js/Pages/BuyViewProductMarket.vue | 323 ---- resources/js/Pages/CartProductMarket.vue | 366 ---- resources/js/Pages/CoopMemberSearch.vue | 112 -- resources/js/Pages/CooperativeDetail.vue | 238 --- resources/js/Pages/CooperativeHub.vue | 72 - resources/js/Pages/CooperativeList.vue | 216 --- .../js/Pages/CooperativeMemberRegister.vue | 493 ----- resources/js/Pages/CreateCoopUser.vue | 198 -- resources/js/Pages/CreateCooperative.vue | 364 ---- resources/js/Pages/CreateOrganization.vue | 289 --- .../js/Pages/CreateProductStoreOwner.vue | 880 --------- resources/js/Pages/CreateProductUltimate.vue | 954 ---------- resources/js/Pages/CreateStore.vue | 733 -------- resources/js/Pages/EditProductUltimate.vue | 617 ------- resources/js/Pages/EditStoreUltimate.vue | 500 ----- resources/js/Pages/EnrollFarmer.vue | 304 --- resources/js/Pages/FarmerProfileEdit.vue | 122 -- resources/js/Pages/Home.vue | 274 +-- resources/js/Pages/ListProductsMarket.vue | 139 -- resources/js/Pages/ListProperties.vue | 96 - resources/js/Pages/ListReferrals.vue | 94 - resources/js/Pages/ListReports.vue | 107 -- resources/js/Pages/ListStores.vue | 145 -- resources/js/Pages/ManageAccounts.vue | 655 ------- .../js/Pages/ManageGlobalTransactions.vue | 358 ---- resources/js/Pages/ManageProductAdmin.vue | 314 ---- resources/js/Pages/ManageProductsAdmin.vue | 613 ------- resources/js/Pages/ManageStoresAdmin.vue | 384 ---- resources/js/Pages/MyStores.vue | 355 ---- resources/js/Pages/MyWallet.vue | 364 ---- resources/js/Pages/PhotoViewer.vue | 206 --- resources/js/Pages/PosAccessKeys.vue | 372 ---- resources/js/Pages/PosHistory.vue | 161 -- resources/js/Pages/PosMain.vue | 1087 ----------- resources/js/Pages/RegisterCoop.vue | 493 ----- .../js/Pages/RemoveProductFromStoreAdmin.vue | 271 --- resources/js/Pages/ShipmentDetail.vue | 141 -- resources/js/Pages/ShipmentList.vue | 100 - resources/js/Pages/TransferCredit.vue | 143 -- resources/js/Pages/TransferMyCredit.vue | 219 --- resources/js/Pages/UltimateConsole.vue | 1627 ----------------- resources/js/Pages/VerificationDashboard.vue | 108 -- resources/js/Pages/ViewAllPhotos.vue | 250 --- resources/js/Pages/ViewStoreMarket.vue | 580 ------ resources/js/composables/Core/useAuth.js | 113 +- .../js/composables/Market/usePosSession.js | 162 -- .../js/composables/useGlobalTransactions.js | 29 - resources/js/composables/usePhotoList.js | 56 - resources/js/composables/useUltimate.js | 204 --- .../composables/useUserAdditionalDetails.js | 131 -- resources/js/stores/globalTransaction.js | 58 - resources/js/stores/pos.js | 439 ----- resources/js/stores/product.js | 218 --- resources/js/stores/store.js | 160 -- resources/js/utils/UserTypes.js | 67 +- routes/web.php | 1079 +++-------- 234 files changed, 5582 insertions(+), 39457 deletions(-) delete mode 100644 app/Console/Commands/ImportReadmeMemberData.php delete mode 100644 app/Console/Commands/SeedDemoProducts.php delete mode 100644 app/Http/Controllers/Accounting/AccountingController.php create mode 100644 app/Http/Controllers/Admin/AdminConsoleController.php delete mode 100644 app/Http/Controllers/Admin/PosAccessKeyController.php create mode 100644 app/Http/Controllers/Admin/UserController.php create mode 100644 app/Http/Controllers/Auth/LoginController.php create mode 100644 app/Http/Controllers/Barangay/BlotterController.php create mode 100644 app/Http/Controllers/Barangay/BlotterHearingController.php create mode 100644 app/Http/Controllers/Barangay/BudgetController.php create mode 100644 app/Http/Controllers/Barangay/DocumentRequestController.php create mode 100644 app/Http/Controllers/Barangay/HouseholdController.php create mode 100644 app/Http/Controllers/Barangay/ProjectController.php create mode 100644 app/Http/Controllers/Barangay/RequestTypeController.php create mode 100644 app/Http/Controllers/Barangay/ResidentController.php create mode 100644 app/Http/Controllers/ChapterController.php create mode 100644 app/Http/Controllers/FileController.php delete mode 100644 app/Http/Controllers/FilesMainController.php delete mode 100644 app/Http/Controllers/GlobalTransactionController.php delete mode 100644 app/Http/Controllers/Helpers/Permissions/ProductPermissions.php delete mode 100644 app/Http/Controllers/Market/ActivityController.php delete mode 100644 app/Http/Controllers/Market/BatchController.php delete mode 100644 app/Http/Controllers/Market/CartController.php delete mode 100644 app/Http/Controllers/Market/CooperativeController.php delete mode 100644 app/Http/Controllers/Market/CooperativeDocumentController.php delete mode 100644 app/Http/Controllers/Market/CreditController.php delete mode 100644 app/Http/Controllers/Market/FarmerController.php delete mode 100644 app/Http/Controllers/Market/GovernanceController.php delete mode 100644 app/Http/Controllers/Market/PerformanceController.php delete mode 100644 app/Http/Controllers/Market/PosController.php delete mode 100644 app/Http/Controllers/Market/ProductController.php delete mode 100644 app/Http/Controllers/Market/ProductPhotoSearchController.php delete mode 100644 app/Http/Controllers/Market/ShipmentController.php delete mode 100644 app/Http/Controllers/Market/StoreController.php delete mode 100644 app/Http/Controllers/Market/UltimateController.php delete mode 100644 app/Http/Controllers/Market/UserInfoController.php delete mode 100644 app/Http/Controllers/Market/UserSettingsController.php delete mode 100644 app/Http/Controllers/PageMemoryController.php delete mode 100644 app/Http/Controllers/Pages/AccountSettingsPageController.php delete mode 100644 app/Http/Controllers/Pages/Core/ApplicationController.php delete mode 100644 app/Http/Controllers/Pages/Core/HomeController.php delete mode 100644 app/Http/Controllers/Pages/PageController.php delete mode 100644 app/Http/Controllers/Pages/TransferMyCreditPageController.php delete mode 100644 app/Http/Controllers/Pages/UserListPageController.php delete mode 100644 app/Http/Controllers/Pages/UserModifyAdminPageController.php create mode 100644 app/Http/Controllers/Payment/QRPHController.php delete mode 100644 app/Http/Controllers/Photos/PhotoGallery.php delete mode 100644 app/Http/Controllers/Property/PropertyManagementController.php delete mode 100644 app/Http/Controllers/Subscription/SubscriptionController.php delete mode 100644 app/Http/Controllers/Subscription/SubscriptionPlanController.php delete mode 100644 app/Http/Controllers/UserCreateController.php delete mode 100644 app/Http/Controllers/UserManagement/CreateUserControllerUltimate.php delete mode 100644 app/Http/Controllers/UserManagement/UserAdditionalDetailsController.php delete mode 100644 app/Http/Controllers/UserPages/UltimateUserController.php delete mode 100644 app/Models/Accounting/Account.php delete mode 100644 app/Models/Accounting/AccountTransaction.php delete mode 100644 app/Models/Accounting/MemberLedger.php create mode 100644 app/Models/Barangay/BarangayBudget.php create mode 100644 app/Models/Barangay/BarangayProject.php create mode 100644 app/Models/Barangay/Blotter.php create mode 100644 app/Models/Barangay/BlotterHearing.php create mode 100644 app/Models/Barangay/DocumentRequest.php create mode 100644 app/Models/Barangay/Household.php create mode 100644 app/Models/Barangay/HouseholdMember.php create mode 100644 app/Models/Barangay/RequestPayment.php create mode 100644 app/Models/Barangay/RequestType.php create mode 100644 app/Models/Barangay/Resident.php delete mode 100644 app/Models/Generic/TableLog.php delete mode 100644 app/Models/GlobalTransaction.php delete mode 100644 app/Models/Market/Cart.php delete mode 100644 app/Models/Market/CartItem.php delete mode 100644 app/Models/Market/CooperativeDocument.php delete mode 100644 app/Models/Market/CooperativeMember.php delete mode 100644 app/Models/Market/CooperativeResolution.php delete mode 100644 app/Models/Market/CooperativeVote.php delete mode 100644 app/Models/Market/Courier.php delete mode 100644 app/Models/Market/Customer.php delete mode 100644 app/Models/Market/FarmerProfile.php delete mode 100644 app/Models/Market/MainOrganization.php delete mode 100644 app/Models/Market/Organization.php delete mode 100644 app/Models/Market/PosAccessKey.php delete mode 100644 app/Models/Market/PosSession.php delete mode 100644 app/Models/Market/PosSessionArchive.php delete mode 100644 app/Models/Market/PosTransaction.php delete mode 100644 app/Models/Market/Product.php delete mode 100644 app/Models/Market/ProductTransaction.php delete mode 100644 app/Models/Market/ProductTransactionSession.php delete mode 100644 app/Models/Market/ProductTransactionSessionArchive.php delete mode 100644 app/Models/Market/Shipment.php delete mode 100644 app/Models/Market/Store.php delete mode 100644 app/Models/Market/StoreManager.php delete mode 100644 app/Models/Market/UserInfo.php delete mode 100644 app/Models/Property/Property.php delete mode 100644 app/Models/Property/Referral.php delete mode 100644 app/Models/Property/ReferralKey.php delete mode 100644 app/Models/Subscription/Subscription.php delete mode 100644 app/Models/Subscription/SubscriptionInvoice.php delete mode 100644 app/Models/Subscription/SubscriptionPlan.php delete mode 100644 database/migrations/2025_09_08_132613_create_market_products_stores_table.php delete mode 100644 database/migrations/2025_10_06_155745_create_product_store_table.php delete mode 100644 database/migrations/2025_11_25_10949_create_accounting_table.php delete mode 100644 database/migrations/2026_03_23_000000_create_global_transactions_table.php delete mode 100644 database/migrations/2026_03_23_000001_create_pos_tables.php delete mode 100644 database/migrations/2026_03_23_000002_create_pos_access_keys_table.php delete mode 100644 database/migrations/2026_03_23_000003_add_expiry_to_pos_access_keys_table.php delete mode 100644 database/migrations/2026_03_23_000004_add_updated_by_to_pos_tables.php delete mode 100644 database/migrations/2026_03_23_000005_add_missing_columns_to_pos_transactions.php delete mode 100644 database/migrations/2026_03_25_000001_add_updated_by_and_is_active_to_pos_access_keys.php delete mode 100644 database/migrations/2026_03_25_000002_add_flow_to_global_transactions_table.php delete mode 100644 database/migrations/2026_03_25_000003_create_customers_table.php delete mode 100644 database/migrations/2026_03_25_000252_remove_unique_constraint_from_pos_sessions_access_key.php delete mode 100644 database/migrations/2026_03_25_204500_create_property_management_tables.php delete mode 100644 database/migrations/2026_03_26_000001_create_couriers_table.php delete mode 100644 database/migrations/2026_03_26_000002_create_shipments_table.php delete mode 100644 database/migrations/2026_03_26_000003_create_farmer_management_tables.php delete mode 100644 database/migrations/2026_03_26_010001_create_cooperative_members_table.php delete mode 100644 database/migrations/2026_03_28_000002_create_carts_and_cart_items_tables.php delete mode 100644 database/migrations/2026_03_28_093000_add_cooperative_details_to_organizations_table.php delete mode 100644 database/migrations/2026_03_30_000001_add_compound_index_to_pos_transactions.php delete mode 100644 database/migrations/2026_04_02_000002_expand_cooperative_members_table.php delete mode 100644 database/migrations/2026_04_02_191649_create_store_managers_table.php delete mode 100644 database/migrations/2026_04_17_000002_create_groups_table.php delete mode 100644 database/migrations/2026_04_17_000003_create_group_members_table.php delete mode 100644 database/migrations/2026_04_17_194347_create_cooperative_resolutions_table.php delete mode 100644 database/migrations/2026_04_17_194348_create_cooperative_votes_table.php delete mode 100644 database/migrations/2026_04_18_040337_create_cooperative_documents_table.php delete mode 100644 database/migrations/2026_04_18_042400_add_versioning_to_cooperative_documents_table.php delete mode 100644 database/migrations/2026_04_18_043210_create_member_ledgers_table.php delete mode 100644 database/migrations/2026_04_19_000001_expand_cooperative_members_program_data.php delete mode 100644 database/migrations/2026_04_19_000002_change_priority_sector_to_json_in_cooperative_members.php delete mode 100644 database/migrations/2026_05_06_120000_create_org_str_table.php delete mode 100644 database/migrations/2026_05_07_120000_create_main_organizations_table.php delete mode 100644 database/migrations/2026_05_08_120000_add_theme_stamp_to_accounts.php delete mode 100644 database/migrations/2026_05_08_120100_add_default_flow_to_accounts.php delete mode 100644 database/migrations/2026_05_22_000001_add_store_scope_to_accounting.php delete mode 100644 database/migrations/2026_05_24_000001_create_subscriptions_tables.php delete mode 100644 database/migrations/2026_05_24_100001_add_default_org_type_system_setting.php delete mode 100644 database/migrations/2026_05_26_000001_add_bible_verse_system_settings.php delete mode 100644 database/migrations/2026_05_28_000001_add_cooperative_id_to_chapters.php delete mode 100644 database/migrations/2026_05_28_000002_add_role_to_chapter_members.php delete mode 100644 resources/js/Pages/AccountingDashboard.vue delete mode 100644 resources/js/Pages/AddProductsToStore.vue delete mode 100644 resources/js/Pages/AddTransaction.vue create mode 100644 resources/js/Pages/AdminConsole.vue delete mode 100644 resources/js/Pages/AssignChapterOfficer.vue delete mode 100644 resources/js/Pages/AssignProductToStore.vue create mode 100644 resources/js/Pages/Barangay/BlotterDetail.vue create mode 100644 resources/js/Pages/Barangay/BudgetLedger.vue create mode 100644 resources/js/Pages/Barangay/DocumentRequestDetail.vue create mode 100644 resources/js/Pages/Barangay/ManageBlotters.vue create mode 100644 resources/js/Pages/Barangay/ManageDocumentRequests.vue create mode 100644 resources/js/Pages/Barangay/ManageHouseholds.vue create mode 100644 resources/js/Pages/Barangay/ManageProjects.vue create mode 100644 resources/js/Pages/Barangay/ManageRequestTypes.vue create mode 100644 resources/js/Pages/Barangay/ManageResidents.vue create mode 100644 resources/js/Pages/Barangay/RequestDocument.vue create mode 100644 resources/js/Pages/Barangay/ResidentProfile.vue delete mode 100644 resources/js/Pages/BatchAddCooperativeMembers.vue delete mode 100644 resources/js/Pages/BatchAddCooperatives.vue delete mode 100644 resources/js/Pages/BatchAddProducts.vue delete mode 100644 resources/js/Pages/BatchAddStores.vue delete mode 100644 resources/js/Pages/BatchAddUsers.vue delete mode 100644 resources/js/Pages/BuyViewProductMarket.vue delete mode 100644 resources/js/Pages/CartProductMarket.vue delete mode 100644 resources/js/Pages/CoopMemberSearch.vue delete mode 100644 resources/js/Pages/CooperativeDetail.vue delete mode 100644 resources/js/Pages/CooperativeHub.vue delete mode 100644 resources/js/Pages/CooperativeList.vue delete mode 100644 resources/js/Pages/CooperativeMemberRegister.vue delete mode 100644 resources/js/Pages/CreateCoopUser.vue delete mode 100644 resources/js/Pages/CreateCooperative.vue delete mode 100644 resources/js/Pages/CreateOrganization.vue delete mode 100644 resources/js/Pages/CreateProductStoreOwner.vue delete mode 100644 resources/js/Pages/CreateProductUltimate.vue delete mode 100644 resources/js/Pages/CreateStore.vue delete mode 100644 resources/js/Pages/EditProductUltimate.vue delete mode 100644 resources/js/Pages/EditStoreUltimate.vue delete mode 100644 resources/js/Pages/EnrollFarmer.vue delete mode 100644 resources/js/Pages/FarmerProfileEdit.vue delete mode 100644 resources/js/Pages/ListProductsMarket.vue delete mode 100644 resources/js/Pages/ListProperties.vue delete mode 100644 resources/js/Pages/ListReferrals.vue delete mode 100644 resources/js/Pages/ListReports.vue delete mode 100644 resources/js/Pages/ListStores.vue delete mode 100644 resources/js/Pages/ManageAccounts.vue delete mode 100644 resources/js/Pages/ManageGlobalTransactions.vue delete mode 100644 resources/js/Pages/ManageProductAdmin.vue delete mode 100644 resources/js/Pages/ManageProductsAdmin.vue delete mode 100644 resources/js/Pages/ManageStoresAdmin.vue delete mode 100644 resources/js/Pages/MyStores.vue delete mode 100644 resources/js/Pages/MyWallet.vue delete mode 100644 resources/js/Pages/PhotoViewer.vue delete mode 100644 resources/js/Pages/PosAccessKeys.vue delete mode 100644 resources/js/Pages/PosHistory.vue delete mode 100644 resources/js/Pages/PosMain.vue delete mode 100644 resources/js/Pages/RegisterCoop.vue delete mode 100644 resources/js/Pages/RemoveProductFromStoreAdmin.vue delete mode 100644 resources/js/Pages/ShipmentDetail.vue delete mode 100644 resources/js/Pages/ShipmentList.vue delete mode 100644 resources/js/Pages/TransferCredit.vue delete mode 100644 resources/js/Pages/TransferMyCredit.vue delete mode 100644 resources/js/Pages/UltimateConsole.vue delete mode 100644 resources/js/Pages/VerificationDashboard.vue delete mode 100644 resources/js/Pages/ViewAllPhotos.vue delete mode 100644 resources/js/Pages/ViewStoreMarket.vue delete mode 100644 resources/js/composables/Market/usePosSession.js delete mode 100644 resources/js/composables/useGlobalTransactions.js delete mode 100644 resources/js/composables/usePhotoList.js delete mode 100644 resources/js/composables/useUltimate.js delete mode 100644 resources/js/composables/useUserAdditionalDetails.js delete mode 100644 resources/js/stores/globalTransaction.js delete mode 100644 resources/js/stores/pos.js delete mode 100644 resources/js/stores/product.js delete mode 100644 resources/js/stores/store.js diff --git a/app/Console/Commands/ImportReadmeMemberData.php b/app/Console/Commands/ImportReadmeMemberData.php deleted file mode 100644 index 5081292..0000000 --- a/app/Console/Commands/ImportReadmeMemberData.php +++ /dev/null @@ -1,78 +0,0 @@ -setDescription('Import member data from README.md for Rex Moran Loba'); - } - - public function handle() - { - $email = 'rexm.loba@gmail.com'; - $user = User::where('email', $email)->first(); - - if (!$user) { - $this->output->info("User with email {$email} not found. Creating..."); - $user = User::create([ - 'name' => 'Rex Moran Loba', - 'fullname' => 'Rex Moran Loba', - 'email' => $email, - 'username' => 'rexmloba', - 'password' => password_hash('password', PASSWORD_DEFAULT), - 'hashkey' => bin2hex(random_bytes(16)), - 'active' => true, - ]); - } - - $userInfo = $user->userInfo; - if (!$userInfo) { - $userInfo = new UserInfo(['user_id' => $user->id]); - $userInfo->hashkey = bin2hex(random_bytes(16)); - } - - $userInfo->firstname = 'Rex Moran'; - $userInfo->lastname = 'Loba'; - $userInfo->email = $email; - $userInfo->is_active = true; - - // Example address data based on feedback - $userInfo->addresses = [ - [ - 'house_no' => 'Unit 1234', - 'street' => 'Street Name', - 'barangay' => 'Barangay Name', - 'city' => 'City Name', - 'province' => 'Province Name', - 'region' => 'Region Name', - 'country' => 'Philippines', - 'zipcode' => '1234' - ] - ]; - - if ($userInfo->save()) { - $this->output->success("Profile for Rex Moran Loba updated successfully."); - } else { - $this->output->error("Failed to update profile."); - } - } -} diff --git a/app/Console/Commands/SeedDemoProducts.php b/app/Console/Commands/SeedDemoProducts.php deleted file mode 100644 index d403fab..0000000 --- a/app/Console/Commands/SeedDemoProducts.php +++ /dev/null @@ -1,90 +0,0 @@ -argument('store_hash'); - - $store = Store::where('hashkey', $storeHash)->first(); - if (! $store) { - $this->error('Store not found.'); - return; - } - - $ultimateUser = User::where('acct_type', 'ULTIMATE')->first(); - if (! $ultimateUser) { - $this->error('No ULTIMATE user found.'); - return; - } - - Auth::loginUsingId($ultimateUser->id); - - $products = [ - ['name' => 'Organic White Rice (25kg sack)', 'category' => 'Grains & Cereals', 'subcategory' => 'Rice', 'price' => 1200, 'unit' => 'sack', 'available' => 100], - ['name' => 'Organic Brown Rice (5kg)', 'category' => 'Grains & Cereals', 'subcategory' => 'Rice', 'price' => 280, 'unit' => 'kg', 'available' => 200], - ['name' => 'White Corn Grits', 'category' => 'Grains & Cereals', 'subcategory' => 'Corn', 'price' => 85, 'unit' => 'kg', 'available' => 300], - ['name' => 'Yellow Corn (fresh ears)', 'category' => 'Grains & Cereals', 'subcategory' => 'Corn', 'price' => 35, 'unit' => 'piece', 'available' => 500], - ['name' => 'Fresh Kangkong (Water Spinach)', 'category' => 'Fresh Produce', 'subcategory' => 'Leafy Vegetables', 'price' => 25, 'unit' => 'bundle', 'available' => 150], - ['name' => 'Pechay (Bok Choy)', 'category' => 'Fresh Produce', 'subcategory' => 'Leafy Vegetables', 'price' => 30, 'unit' => 'bundle', 'available' => 150], - ['name' => 'Ampalaya (Bitter Gourd)', 'category' => 'Fresh Produce', 'subcategory' => 'Vegetables', 'price' => 60, 'unit' => 'kg', 'available' => 100], - ['name' => 'Sitaw (String Beans)', 'category' => 'Fresh Produce', 'subcategory' => 'Vegetables', 'price' => 55, 'unit' => 'bundle', 'available' => 120], - ['name' => 'Kamote (Sweet Potato)', 'category' => 'Fresh Produce', 'subcategory' => 'Root Crops', 'price' => 40, 'unit' => 'kg', 'available' => 200], - ['name' => 'Gabi (Taro Root)', 'category' => 'Fresh Produce', 'subcategory' => 'Root Crops', 'price' => 50, 'unit' => 'kg', 'available' => 150], - ['name' => 'Sayote (Chayote)', 'category' => 'Fresh Produce', 'subcategory' => 'Vegetables', 'price' => 45, 'unit' => 'kg', 'available' => 180], - ['name' => 'Labanos (Radish)', 'category' => 'Fresh Produce', 'subcategory' => 'Root Crops', 'price' => 30, 'unit' => 'bundle', 'available' => 100], - ['name' => 'Fresh Tomatoes', 'category' => 'Fresh Produce', 'subcategory' => 'Vegetables', 'price' => 70, 'unit' => 'kg', 'available' => 200], - ['name' => 'Carabao Mango (green)', 'category' => 'Fresh Produce', 'subcategory' => 'Fruits', 'price' => 90, 'unit' => 'kg', 'available' => 100], - ['name' => 'Banana (Latundan)', 'category' => 'Fresh Produce', 'subcategory' => 'Fruits', 'price' => 65, 'unit' => 'hand', 'available' => 80], - ['name' => 'Pineapple (Bukidnon)', 'category' => 'Fresh Produce', 'subcategory' => 'Fruits', 'price' => 80, 'unit' => 'piece', 'available' => 60], - ['name' => 'Native Chicken (live)', 'category' => 'Livestock & Poultry', 'subcategory' => 'Poultry', 'price' => 350, 'unit' => 'piece', 'available' => 30], - ['name' => 'Free-range Eggs', 'category' => 'Livestock & Poultry', 'subcategory' => 'Eggs', 'price' => 12, 'unit' => 'piece', 'available' => 500], - ['name' => 'Fresh Tilapia', 'category' => 'Seafood', 'subcategory' => 'Freshwater Fish', 'price' => 130, 'unit' => 'kg', 'available' => 50], - ['name' => 'Bangus (Milkfish)', 'category' => 'Seafood', 'subcategory' => 'Saltwater Fish', 'price' => 180, 'unit' => 'kg', 'available' => 50], - ['name' => 'Carabao Milk (1L)', 'category' => 'Processed Goods', 'subcategory' => 'Dairy', 'price' => 120, 'unit' => 'liter', 'available' => 60], - ['name' => 'Coconut Oil (Virgin, 1L)', 'category' => 'Processed Goods', 'subcategory' => 'Oils', 'price' => 250, 'unit' => 'bottle', 'available' => 40], - ['name' => 'Organic Peanuts (roasted)', 'category' => 'Processed Goods', 'subcategory' => 'Nuts', 'price' => 95, 'unit' => '250g pack', 'available' => 80], - ['name' => 'Muscovado Sugar', 'category' => 'Processed Goods', 'subcategory' => 'Sweeteners', 'price' => 110, 'unit' => '250g pack', 'available' => 60], - ['name' => 'Fresh Turmeric (Luyang Dilaw)', 'category' => 'Fresh Produce', 'subcategory' => 'Herbs & Spices', 'price' => 60, 'unit' => '100g pack', 'available' => 100], - ]; - - $created = []; - - foreach ($products as $item) { - $product = new Product(); - $product->name = $item['name']; - $product->category = $item['category']; - $product->subcategory = $item['subcategory']; - $product->price = $item['price']; - $product->unitname = $item['unit']; - $product->description = $item['name']; - $product->is_active = true; - $product->save(); - - $store->products()->attach($product->id, [ - 'price' => $item['price'], - 'available' => $item['available'], - 'is_active' => true, - ]); - - $created[] = $product; - } - - $count = count($created); - $this->info("Created {$count} products and attached to store {$store->name}"); - } -} diff --git a/app/Http/Controllers/Accounting/AccountingController.php b/app/Http/Controllers/Accounting/AccountingController.php deleted file mode 100644 index 2a0b751..0000000 --- a/app/Http/Controllers/Accounting/AccountingController.php +++ /dev/null @@ -1,751 +0,0 @@ -acct_type instanceof UserTypes - ? $user->acct_type - : (UserTypes::tryFrom($user->acct_type) ?? UserTypes::PUBLIC); - } - - private function isBig3(UserTypes $t): bool - { - return in_array($t, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR], true); - } - - private function isStoreLevel(UserTypes $t): bool - { - return in_array($t, [UserTypes::STORE_OWNER, UserTypes::STORE_MANAGER], true); - } - - /** IDs of stores the current user owns or manages (including descendants). */ - private function userStoreIds(): array - { - $user = Auth::user(); - $allowedUserIds = array_merge([$user->id], $user->getAllDescendants()->pluck('id')->toArray()); - - return Store::where(function ($q) use ($allowedUserIds) { - $q->whereIn('owner_id', $allowedUserIds) - ->orWhereIn('manager_id', $allowedUserIds) - ->orWhereHas('managers', function ($mq) use ($allowedUserIds) { - $mq->whereIn('user_id', $allowedUserIds); - }); - })->pluck('id')->toArray(); - } - - /** Apply store_id scope to a query based on the caller's role. */ - private function scopeAccounts($query, array $storeIds, bool $isBig3) - { - if ($isBig3) { - return $query->whereNull('store_id'); - } - return $query->whereIn('store_id', $storeIds); - } - - /** Check store-level access. Returns false if the accounting_store module is disabled. */ - private function guardStore(): bool - { - return ModuleHelper::isEnabled('accounting_store'); - } - - // ─── public endpoints ──────────────────────────────────────────────────── - - /** - * Full Chart of Accounts tree. - */ - public function getAccountsTree(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) { - return ResponseHelper::returnUnauthorized(); - } - - $type = $this->resolveType(); - $isBig3 = $this->isBig3($type); - - if (!$isBig3 && !$this->guardStore()) { - return ResponseHelper::returnUnauthorized(); - } - - $storeIds = $isBig3 ? [] : $this->userStoreIds(); - - $base = Account::whereNull('parent_id')->where('is_active', true); - $base = $this->scopeAccounts($base, $storeIds, $isBig3); - - $accounts = $base - ->with(['children' => function ($q) use ($storeIds, $isBig3) { - $q = $q->where('is_active', true); - $q = $this->scopeAccounts($q, $storeIds, $isBig3); - $q->with(['children' => function ($q2) use ($storeIds, $isBig3) { - $q2 = $q2->where('is_active', true); - $q2 = $this->scopeAccounts($q2, $storeIds, $isBig3); - }]); - }]) - ->orderBy('id') - ->get(); - - return response()->json(['success' => true, 'data' => $accounts]); - } - - /** - * Leaf accounts for the Daily Entry form. - */ - public function getLeafAccounts(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) { - return ResponseHelper::returnUnauthorized(); - } - - $type = $this->resolveType(); - $isBig3 = $this->isBig3($type); - - if (!$isBig3 && !$this->guardStore()) { - return ResponseHelper::returnUnauthorized(); - } - - $storeIds = $isBig3 ? [] : $this->userStoreIds(); - - $base = Account::where('is_active', true)->whereDoesntHave('children'); - $base = $this->scopeAccounts($base, $storeIds, $isBig3); - - $leafAccounts = $base - ->with('parent.parent') - ->orderBy('id') - ->get(); - - return response()->json(['success' => true, 'data' => $leafAccounts]); - } - - /** - * Daily transactions for a given date. - */ - public function getDailyTransactions(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) { - return ResponseHelper::returnUnauthorized(); - } - - $type = $this->resolveType(); - $isBig3 = $this->isBig3($type); - - if (!$isBig3 && !$this->guardStore()) { - return ResponseHelper::returnUnauthorized(); - } - - $storeIds = $isBig3 ? [] : $this->userStoreIds(); - $date = $request->input('date', date('Y-m-d')); - - $accountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id'); - - $transactions = AccountTransaction::with(['account']) - ->whereIn('account_id', $accountIds) - ->whereDate('transaction_date', $date) - ->get(); - - $mapped = []; - foreach ($transactions as $txn) { - $mapped[$txn->account_id] = [ - 'id' => $txn->id, - 'amount' => $txn->amount, - 'notes' => $txn->notes, - ]; - } - - return response()->json(['success' => true, 'date' => $date, 'data' => $mapped]); - } - - /** - * Bulk save daily transactions. - */ - public function saveDailyTransactions(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) { - return ResponseHelper::returnUnauthorized(); - } - - $type = $this->resolveType(); - $isBig3 = $this->isBig3($type); - - if (!$isBig3 && !$this->guardStore()) { - return ResponseHelper::returnUnauthorized(); - } - - $storeIds = $isBig3 ? [] : $this->userStoreIds(); - $date = $request->input('date'); - $entries = $request->input('entries', []); - $userId = Auth::id(); - - // Allowed account IDs for this user (prevent writing to other stores' accounts) - $allowedAccountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id')->flip()->all(); - - DB::beginTransaction(); - try { - foreach ($entries as $accountId => $entryData) { - if (!array_key_exists($accountId, $allowedAccountIds)) { - continue; // skip accounts not owned by this user - } - - $amount = (float)($entryData['amount'] ?? 0); - $account = Account::find($accountId); - if (!$account) { - continue; - } - - $flow = $account->default_flow - ?: ((strtoupper((string) $account->type) === 'REVENUE' || strtoupper((string) $account->type) === 'LIABILITY') ? 'INCOME' : 'EXPENSE'); - - $txn = AccountTransaction::where('account_id', $accountId) - ->whereDate('transaction_date', $date) - ->first(); - - if ($amount == 0) { - if ($txn) { - $txn->delete(); - } - continue; - } - - if ($txn) { - $txn->amount = $amount; - $txn->notes = $entryData['notes'] ?? null; - $txn->updated_by = $userId; - $txn->save(); - } else { - AccountTransaction::create([ - 'hashkey' => Str::random(32), - 'account_id' => $accountId, - 'amount' => $amount, - 'flow' => $flow, - 'transaction_date' => $date, - 'notes' => $entryData['notes'] ?? null, - 'created_by' => $userId, - 'updated_by' => $userId, - ]); - } - } - DB::commit(); - return response()->json(['success' => true, 'message' => 'Daily transactions saved.']); - } catch (\Exception $e) { - DB::rollBack(); - return response()->json(['success' => false, 'message' => 'Error saving: ' . $e->getMessage()]); - } - } - - /** - * Transaction list for CRUD tab. - */ - public function listTransactions(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) { - return ResponseHelper::returnUnauthorized(); - } - - $type = $this->resolveType(); - $isBig3 = $this->isBig3($type); - - if (!$isBig3 && !$this->guardStore()) { - return ResponseHelper::returnUnauthorized(); - } - - $storeIds = $isBig3 ? [] : $this->userStoreIds(); - $accountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id'); - - $query = AccountTransaction::with(['account', 'creator', 'updater']) - ->whereIn('account_id', $accountIds); - - if ($request->filled('account_id')) { - $query->where('account_id', $request->account_id); - } - if ($request->filled('date_from')) { - $query->whereDate('transaction_date', '>=', $request->date_from); - } - if ($request->filled('date_to')) { - $query->whereDate('transaction_date', '<=', $request->date_to); - } - - $transactions = $query->orderBy('transaction_date', 'desc')->paginate(50); - - return response()->json(['success' => true, 'data' => $transactions]); - } - - /** - * Delete single transaction. - */ - public function deleteTransaction(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) { - return ResponseHelper::returnUnauthorized(); - } - - $type = $this->resolveType(); - $isBig3 = $this->isBig3($type); - $storeIds = $isBig3 ? [] : $this->userStoreIds(); - $allowedAccountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id')->all(); - - $txn = AccountTransaction::find($request->id); - if ($txn && in_array($txn->account_id, $allowedAccountIds)) { - $txn->delete(); - } - - return response()->json(['success' => true]); - } - - /** - * Monthly matrix report (Rows = Days, Columns = Accounts). - */ - public function getMonthlyReport(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) { - return ResponseHelper::returnUnauthorized(); - } - - $type = $this->resolveType(); - $isBig3 = $this->isBig3($type); - - if (!$isBig3 && !$this->guardStore()) { - return ResponseHelper::returnUnauthorized(); - } - - $storeIds = $isBig3 ? [] : $this->userStoreIds(); - $year = $request->input('year', date('Y')); - $month = $request->input('month', date('m')); - - $accountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id')->all(); - - $transactions = AccountTransaction::whereYear('transaction_date', $year) - ->whereMonth('transaction_date', $month) - ->whereIn('account_id', $accountIds) - ->get(); - - $daysInMonth = cal_days_in_month(CAL_GREGORIAN, (int)$month, (int)$year); - $matrix = []; - for ($i = 1; $i <= $daysInMonth; $i++) { - $matrix[$i] = []; - } - - foreach ($transactions as $txn) { - $day = (int)date('d', strtotime($txn->transaction_date)); - $matrix[$day][$txn->account_id] = ($matrix[$day][$txn->account_id] ?? 0) + $txn->amount; - } - - $columnTotals = []; - foreach ($transactions as $txn) { - $columnTotals[$txn->account_id] = ($columnTotals[$txn->account_id] ?? 0) + $txn->amount; - } - - return response()->json([ - 'success' => true, - 'year' => $year, - 'month' => $month, - 'days_in_month' => $daysInMonth, - 'matrix' => $matrix, - 'column_totals' => $columnTotals, - ]); - } - - /** - * Create a new account node. For store-level users the account is - * automatically scoped to their store (first owned store). - */ - public function createAccount(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) { - return ResponseHelper::returnUnauthorized(); - } - - $type = $this->resolveType(); - $isBig3 = $this->isBig3($type); - - if (!$isBig3 && !$this->guardStore()) { - return ResponseHelper::returnUnauthorized(); - } - - $name = trim((string) $request->input('name', '')); - $accountType = strtoupper(trim((string) $request->input('type', ''))); - $defaultFlow = strtoupper(trim((string) $request->input('default_flow', ''))); - $parentId = $request->input('parent_id') ?: null; - $description = $request->input('description') ?: null; - - if ($name === '' || $accountType === '') { - return response()->json(['success' => false, 'message' => 'Name and type are required.'], 422); - } - if (!in_array($defaultFlow, ['INCOME', 'EXPENSE'], true)) { - $defaultFlow = in_array($accountType, ['REVENUE', 'LIABILITY']) ? 'INCOME' : 'EXPENSE'; - } - if ($parentId !== null && !Account::where('id', $parentId)->exists()) { - return response()->json(['success' => false, 'message' => 'Parent account not found.'], 422); - } - - // Determine store_id: null for Big3 (global), first store for store-level users - $storeId = null; - if (!$isBig3) { - $storeIds = $this->userStoreIds(); - if (empty($storeIds)) { - return response()->json(['success' => false, 'message' => 'No store found for your account. Create a store first.'], 422); - } - // If a parent is supplied, inherit its store_id; otherwise use the request-supplied store_id or the user's first store - if ($parentId) { - $parent = Account::find($parentId); - $storeId = $parent?->store_id ?? $storeIds[0]; - } else { - $requestedStoreId = $request->input('store_id'); - $storeId = ($requestedStoreId && in_array($requestedStoreId, $storeIds)) - ? (int)$requestedStoreId - : $storeIds[0]; - } - } - - try { - $account = Account::create([ - 'hashkey' => Str::random(40), - 'parent_id' => $parentId, - 'store_id' => $storeId, - 'type' => $accountType, - 'default_flow' => $defaultFlow, - 'name' => $name, - 'description' => $description, - 'is_active' => true, - 'created_by' => Auth::id(), - 'updated_by' => Auth::id(), - ]); - } catch (\Throwable $e) { - \Hypervel\Support\Facades\Log::error('createAccount failed', [ - 'message' => $e->getMessage(), - 'name' => $name, - 'type' => $accountType, - 'parent_id' => $parentId, - 'store_id' => $storeId, - 'user_id' => Auth::id(), - ]); - return response()->json([ - 'success' => false, - 'message' => 'Could not create account: ' . $e->getMessage(), - ], 422); - } - - return response()->json(['success' => true, 'data' => $account]); - } - - /** - * Update editable fields (name, type, default_flow, description). - */ - public function updateAccount(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) { - return ResponseHelper::returnUnauthorized(); - } - - $account = Account::find($request->input('id')); - if (!$account) { - return response()->json(['success' => false, 'message' => 'Account not found.'], 404); - } - - // Store-level users may only edit their own store's accounts - $type = $this->resolveType(); - $isBig3 = $this->isBig3($type); - if (!$isBig3) { - if (!$this->guardStore()) { - return ResponseHelper::returnUnauthorized(); - } - $storeIds = $this->userStoreIds(); - if (!in_array($account->store_id, $storeIds)) { - return ResponseHelper::returnUnauthorized(); - } - } - - if ($request->filled('name')) { - $account->name = trim((string) $request->input('name')); - } - if ($request->filled('type')) { - $account->type = strtoupper(trim((string) $request->input('type'))); - } - if ($request->filled('default_flow')) { - $flow = strtoupper(trim((string) $request->input('default_flow'))); - if (!in_array($flow, ['INCOME', 'EXPENSE'], true)) { - return response()->json(['success' => false, 'message' => 'default_flow must be INCOME or EXPENSE.'], 422); - } - $account->default_flow = $flow; - } - if ($request->has('description')) { - $account->description = $request->input('description') ?: null; - } - - $account->updated_by = Auth::id(); - $account->save(); - - return response()->json(['success' => true, 'data' => $account]); - } - - /** - * Archive (soft-disable) an account. - */ - public function archiveAccount(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) { - return ResponseHelper::returnUnauthorized(); - } - - $account = Account::find($request->input('id')); - if (!$account) { - return response()->json(['success' => false, 'message' => 'Account not found.'], 404); - } - - $type = $this->resolveType(); - $isBig3 = $this->isBig3($type); - if (!$isBig3) { - if (!$this->guardStore()) { - return ResponseHelper::returnUnauthorized(); - } - $storeIds = $this->userStoreIds(); - if (!in_array($account->store_id, $storeIds)) { - return ResponseHelper::returnUnauthorized(); - } - } - - if (Account::where('parent_id', $account->id)->where('is_active', true)->exists()) { - return response()->json(['success' => false, 'message' => 'Archive child accounts first.'], 422); - } - if (AccountTransaction::where('account_id', $account->id)->exists()) { - return response()->json(['success' => false, 'message' => 'Cannot archive: account has transactions. Move or delete them first.'], 422); - } - - $account->is_active = false; - $account->updated_by = Auth::id(); - $account->save(); - - return response()->json(['success' => true]); - } - - /** - * Move an account under a new parent (or to root). Refuses cycles. - */ - public function moveAccount(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) { - return ResponseHelper::returnUnauthorized(); - } - - $account = Account::find($request->input('id')); - if (!$account) { - return response()->json(['success' => false, 'message' => 'Account not found.'], 404); - } - - $type = $this->resolveType(); - $isBig3 = $this->isBig3($type); - if (!$isBig3) { - if (!$this->guardStore()) { - return ResponseHelper::returnUnauthorized(); - } - $storeIds = $this->userStoreIds(); - if (!in_array($account->store_id, $storeIds)) { - return ResponseHelper::returnUnauthorized(); - } - } - - $newParentId = $request->input('parent_id'); - $newParentId = ($newParentId === null || $newParentId === '' || $newParentId == 0) ? null : (int)$newParentId; - - if ($newParentId === (int)$account->id) { - return response()->json(['success' => false, 'message' => 'An account cannot be its own parent.'], 422); - } - - if ($newParentId !== null) { - $parent = Account::find($newParentId); - if (!$parent) { - return response()->json(['success' => false, 'message' => 'Target parent not found.'], 422); - } - - $cursor = $parent; - $hops = 0; - while ($cursor && $hops < 1000) { - if ((int)$cursor->id === (int)$account->id) { - return response()->json(['success' => false, 'message' => 'Cannot move an account under one of its own descendants.'], 422); - } - $cursor = $cursor->parent_id ? Account::find($cursor->parent_id) : null; - $hops++; - } - } - - $account->parent_id = $newParentId; - $account->updated_by = Auth::id(); - $account->save(); - - return response()->json(['success' => true]); - } - - /** - * Re-activate an archived account. - */ - public function restoreAccount(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) { - return ResponseHelper::returnUnauthorized(); - } - - $account = Account::find($request->input('id')); - if (!$account) { - return response()->json(['success' => false, 'message' => 'Account not found.'], 404); - } - - $type = $this->resolveType(); - $isBig3 = $this->isBig3($type); - if (!$isBig3) { - if (!$this->guardStore()) { - return ResponseHelper::returnUnauthorized(); - } - $storeIds = $this->userStoreIds(); - if (!in_array($account->store_id, $storeIds)) { - return ResponseHelper::returnUnauthorized(); - } - } - - $account->is_active = true; - $account->updated_by = Auth::id(); - $account->save(); - - return response()->json(['success' => true]); - } - - /** - * Theme metadata + drift summary for Manage Accounts. - */ - public function getThemeInfo(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) { - return ResponseHelper::returnUnauthorized(); - } - - $current = \App\Support\AccountingTheme::current(); - $definition = \App\Support\AccountingTheme::definition($current); - - return response()->json([ - 'success' => true, - 'current' => $current, - 'definition' => [ - 'label' => $definition['label'] ?? $current, - 'description' => $definition['description'] ?? null, - 'version' => $definition['version'] ?? 1, - ], - 'options' => \App\Support\AccountingTheme::options(), - 'counts' => [ - 'theme_tagged' => Account::where('theme_key', $current)->count(), - 'user_added' => Account::whereNull('theme_key')->count(), - 'archived' => Account::where('is_active', false)->count(), - ], - ]); - } - - /** - * Read-only drift report. - */ - public function getThemeDrift(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) { - return ResponseHelper::returnUnauthorized(); - } - - return response()->json([ - 'success' => true, - 'data' => \App\Support\AccountingTheme::drift(), - ]); - } - - /** - * Switch the active accounting theme. - */ - public function setTheme(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) { - return ResponseHelper::returnUnauthorized(); - } - - $key = trim((string) $request->input('key', '')); - if ($key === '' || !\array_key_exists($key, \App\Support\AccountingTheme::all())) { - return response()->json(['success' => false, 'message' => 'Unknown theme.'], 422); - } - - \App\Models\SystemSetting::setValue('accounting_theme', $key, 'accounting', 'string'); - - return response()->json(['success' => true, 'key' => $key]); - } - - /** - * Idempotently re-apply the active theme (additive only). - */ - public function applyTheme(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) { - return ResponseHelper::returnUnauthorized(); - } - - try { - $stats = \App\Support\AccountingTheme::apply(); - } catch (\Throwable $e) { - return response()->json([ - 'success' => false, - 'message' => 'Failed to apply theme: ' . $e->getMessage(), - ], 422); - } - - return response()->json([ - 'success' => true, - 'message' => sprintf( - 'Theme "%s" applied: %d created, %d updated, %d unchanged.', - $stats['theme_key'], - $stats['created'], - $stats['stamped'], - $stats['skipped'] - ), - 'data' => $stats, - ]); - } - - /** - * Recent transactions for the Reports page. - */ - public function listReports(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) { - return ResponseHelper::returnUnauthorized(); - } - - $type = $this->resolveType(); - $isBig3 = $this->isBig3($type); - - if (!$isBig3 && !$this->guardStore()) { - return ResponseHelper::returnUnauthorized(); - } - - $storeIds = $isBig3 ? [] : $this->userStoreIds(); - $accountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id'); - - $transactions = AccountTransaction::with(['account', 'creator']) - ->whereIn('account_id', $accountIds) - ->orderBy('transaction_date', 'desc') - ->limit(100) - ->get(); - - return response()->json(['success' => true, 'transactions' => $transactions]); - } -} diff --git a/app/Http/Controllers/Admin/AdminConsoleController.php b/app/Http/Controllers/Admin/AdminConsoleController.php new file mode 100644 index 0000000..6bc182c --- /dev/null +++ b/app/Http/Controllers/Admin/AdminConsoleController.php @@ -0,0 +1,162 @@ +acct_type, UserActions::UltimateConsole); + } + + public function getSystemStats() + { + if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); + + $globalMessage = Redis::get('system:global_message'); + $redisStatus = ['connected' => false, 'ping_ms' => null, 'used_memory_human' => null, 'version' => null, 'error' => null]; + + try { + $start = microtime(true); + $pong = Redis::ping(); + $redisStatus['ping_ms'] = round((microtime(true) - $start) * 1000, 2); + $redisStatus['connected'] = in_array($pong, [true, 'PONG', '+PONG'], true) || (is_string($pong) && stripos($pong, 'PONG') !== false); + $info = Redis::info(); + if (is_array($info)) { + $flat = isset($info['Memory']) ? $info['Memory'] : $info; + $redisStatus['used_memory_human'] = $flat['used_memory_human'] ?? null; + $serverInfo = isset($info['Server']) ? $info['Server'] : $info; + $redisStatus['version'] = $serverInfo['redis_version'] ?? null; + } + } catch (\Throwable $e) { + $redisStatus['error'] = $e->getMessage(); + } + + $stats = [ + 'users' => User::count(), + 'active_users' => User::where('active', true)->count(), + 'residents' => DB::table('barangay_residents')->count(), + 'households' => DB::table('barangay_households')->count(), + 'blotters' => DB::table('barangay_blotters')->count(), + 'document_requests' => DB::table('barangay_document_requests')->count(), + 'projects' => DB::table('barangay_projects')->count(), + 'announcements' => DB::table('announcements')->count(), + 'php_version' => PHP_VERSION, + 'server_time' => date('Y-m-d H:i:s'), + 'maintenance_mode' => Redis::get('system:maintenance_mode') === 'true', + 'global_message' => $globalMessage ? json_decode($globalMessage, true) : null, + 'logs_count' => DB::table('logs')->count(), + 'table_logs_count' => DB::table('table_logs')->count(), + 'redis' => $redisStatus, + ]; + + return Response::json(['success' => true, 'data' => $stats]); + } + + public function runQuery(Request $request) + { + if (Auth::user()->acct_type !== UserTypes::SUPER_ADMIN || !UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::UltimateQuery)) { + return ResponseHelper::returnUnauthorized(); + } + + $query = $request->input('query'); + if (empty($query)) return ResponseHelper::returnError('Query cannot be empty'); + + $lower = strtolower(trim($query)); + $allowed = str_starts_with($lower, 'select') || str_starts_with($lower, 'show') || str_starts_with($lower, 'describe') || str_starts_with($lower, 'explain'); + if (!$allowed) { + return ResponseHelper::returnError('Only SELECT, SHOW, DESCRIBE, EXPLAIN queries are allowed'); + } + + try { + $results = DB::select($query); + return Response::json(['success' => true, 'data' => $results, 'count' => count($results)]); + } catch (\Throwable $e) { + return ResponseHelper::returnError('Query error: ' . $e->getMessage()); + } + } + + public function setMaintenanceMode(Request $request) + { + if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); + $enabled = (bool) $request->input('enabled', false); + Redis::set('system:maintenance_mode', $enabled ? 'true' : 'false'); + return Response::json(['success' => true, 'maintenance_mode' => $enabled]); + } + + public function setGlobalMessage(Request $request) + { + if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); + $message = $request->input('message'); + if ($message) { + Redis::set('system:global_message', json_encode([ + 'text' => $message, + 'type' => $request->input('type', 'info'), + 'updated_at' => now()->toDateTimeString(), + ])); + } else { + Redis::del('system:global_message'); + } + return Response::json(['success' => true]); + } + + public function clearCache(Request $request) + { + if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); + Redis::flushDB(); + return Response::json(['success' => true, 'message' => 'Cache cleared']); + } + + public function getLogs(Request $request) + { + if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); + $limit = min((int) $request->input('limit', 50), 200); + $logs = DB::table('logs')->orderByDesc('id')->limit($limit)->get(); + return Response::json(['success' => true, 'data' => $logs]); + } + + public function getTableLogs(Request $request) + { + if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); + $limit = min((int) $request->input('limit', 50), 200); + $logs = DB::table('table_logs')->orderByDesc('id')->limit($limit)->get(); + return Response::json(['success' => true, 'data' => $logs]); + } + + public function backupDatabase(Request $request) + { + if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); + + $name = $request->input('name', 'backup_' . date('Y_m_d_His')); + $backup = DbBackup::create([ + 'name' => $name, + 'status' => 'pending', + 'created_by' => Auth::id(), + ]); + + return Response::json(['success' => true, 'data' => $backup, 'message' => 'Backup queued']); + } + + public function listBackups() + { + if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); + $backups = DbBackup::orderByDesc('id')->limit(20)->get(); + return Response::json(['success' => true, 'data' => $backups]); + } +} diff --git a/app/Http/Controllers/Admin/PosAccessKeyController.php b/app/Http/Controllers/Admin/PosAccessKeyController.php deleted file mode 100644 index 47ec03d..0000000 --- a/app/Http/Controllers/Admin/PosAccessKeyController.php +++ /dev/null @@ -1,174 +0,0 @@ -acct_type, UserActions::ViewPosAccessKeys)) { - return ResponseHelper::returnUnauthorized(); - } - - // Auto-deactivate any expired keys - PosAccessKey::autoExpire(); - - $user = Auth::user(); - $acctType = $user->acct_type->value ?? $user->acct_type ?? ''; - - // Ultimate, Super Operator, and Operator see everything - if (in_array($acctType, ['ult', 'super operator', 'operator'])) { - $query = PosAccessKey::with(['store:id,name,hashkey,owner_id', 'store.owner:id,name,nickname,hashkey']) - ->orderBy('id', 'desc'); - } else { - // Non-ultimate users see their own and their descendants' stores' keys - $descendants = $user->getAllDescendants(); - $descendantIds = $descendants->pluck('id')->push($user->id)->toArray(); - - $storeIds = Store::whereIn('owner_id', $descendantIds) - ->orWhereIn('manager_id', $descendantIds) - ->pluck('id') - ->toArray(); - - $query = PosAccessKey::with(['store:id,name,hashkey,owner_id', 'store.owner:id,name,nickname,hashkey']) - ->whereIn('store_id', $storeIds) - ->orderBy('id', 'desc'); - } - - $keys = $query->get(); - - return ResponseHelper::returnSuccessResponse($keys, 'pos_access_keys'); - } - - public function store(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::CreatePosAccessKey)) { - return ResponseHelper::returnUnauthorized(); - } - - $validated = $request->validate([ - 'name' => 'required|string|max:255', - 'store_hash' => 'required|string', - 'expires_at' => 'nullable|string', - ]); - - $store = Store::where('hashkey', $validated['store_hash'])->first(); - if (!$store) { - return ResponseHelper::returnError('Store not found', 404); - } - - $user = Auth::user(); - $acctType = $user->acct_type->value ?? $user->acct_type ?? ''; - if (!in_array($acctType, ['ult', 'super operator', 'operator'])) { - $descendants = $user->getAllDescendants(); - $allowedIds = $descendants->pluck('id')->push($user->id)->toArray(); - if (!in_array($store->owner_id, $allowedIds) && !in_array($store->manager_id, $allowedIds)) { - return ResponseHelper::returnError('Unauthorized to create keys for this store', 403); - } - } - - $data = [ - 'access_key' => 'PK-' . Str::upper(Str::random(16)), - 'store_id' => $store->id, - 'name' => $validated['name'], - 'created_by' => Auth::id(), - 'status' => 'active', - ]; - - // Set expiry if provided - if (!empty($validated['expires_at'])) { - $data['expires_at'] = $validated['expires_at']; - } - - $key = PosAccessKey::create($data); - - return ResponseHelper::returnSuccessResponse($key, $key->hashkey, 'POS Access Key created'); - } - - public function destroy(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::DeletePosAccessKey)) { - return ResponseHelper::returnUnauthorized(); - } - - $hashkey = $request->input('target'); - $key = PosAccessKey::with('store')->where('hashkey', $hashkey)->first(); - - if (!$key) { - return ResponseHelper::returnError('Key not found', 404); - } - - if (!$this->userOwnsKeyStore($key)) { - return ResponseHelper::returnError('Unauthorized to delete this key', 403); - } - - $key->delete(); - return ResponseHelper::returnSuccessResponse([], $hashkey, 'Key deleted'); - } - - public function toggleStatus(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::TogglePosAccessKey)) { - return ResponseHelper::returnUnauthorized(); - } - - $hashkey = $request->input('target'); - $key = PosAccessKey::with('store')->where('hashkey', $hashkey)->first(); - - if (!$key) { - return ResponseHelper::returnError('Key not found', 404); - } - - if (!$this->userOwnsKeyStore($key)) { - return ResponseHelper::returnError('Unauthorized to modify this key', 403); - } - - $key->status = $key->status === 'active' ? 'inactive' : 'active'; - $key->save(); - return ResponseHelper::returnSuccessResponse($key, $hashkey, 'Status updated'); - } - - /** - * Ownership gate shared by destroy/toggleStatus: Big 3 always pass; - * everyone else must own or manage (directly or via descendants) the - * store the key belongs to. - */ - private function userOwnsKeyStore(PosAccessKey $key): bool - { - $user = Auth::user(); - if (!$user) { - return false; - } - - $acctType = $user->acct_type->value ?? $user->acct_type ?? ''; - if (in_array($acctType, ['ult', 'super operator', 'operator'])) { - return true; - } - - $store = $key->store; - if (!$store) { - return false; - } - - $allowedIds = $user->getAllDescendants()->pluck('id')->push($user->id)->toArray(); - return in_array($store->owner_id, $allowedIds) || in_array($store->manager_id, $allowedIds); - } -} - diff --git a/app/Http/Controllers/Admin/SystemSettingsController.php b/app/Http/Controllers/Admin/SystemSettingsController.php index 70448c3..c21d9b1 100644 --- a/app/Http/Controllers/Admin/SystemSettingsController.php +++ b/app/Http/Controllers/Admin/SystemSettingsController.php @@ -155,42 +155,9 @@ class SystemSettingsController $settings['app_logo_url'] = cdn_asset('vendor/assets/icons/192x192.png'); // Fallback to PWA icon on the CDN } - // Resolve main organization hashkey to a usable data object - $settings['main_organization_data'] = null; - if (!empty($settings['main_organization'])) { - $org = \App\Models\Market\Organization::where('hashkey', $settings['main_organization'])->first(); - if ($org) { - $settings['main_organization_data'] = [ - 'hashkey' => $org->hashkey, - 'name' => $org->name, - 'type' => $org->type, - ]; - } - } - return $settings; } - /** - * List organizations available for selection as the main cooperative/organization. - */ - public function listOrganizations() - { - if (!Auth::user()->isUltimate()) { - return ResponseHelper::returnUnauthorized(); - } - - $orgs = \App\Models\Market\Organization::where('is_active', true) - ->orderBy('type') - ->orderBy('name') - ->get(['hashkey', 'name', 'type']); - - return response()->json([ - 'success' => true, - 'data' => $orgs, - ]); - } - /** * Get all modules with their effective state, env/config default, and * any DB-stored override. Used by the Ultimate Console module manager. diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php new file mode 100644 index 0000000..0232809 --- /dev/null +++ b/app/Http/Controllers/Admin/UserController.php @@ -0,0 +1,207 @@ +acct_type, UserActions::ListAllUsersAsParentforUserCreation)) { + return ResponseHelper::returnUnauthorized(); + } + + $query = User::orderByDesc('id'); + + if ($search = $request->input('search')) { + $query->where(function ($q) use ($search) { + $q->where('name', 'like', "%{$search}%") + ->orWhere('mobile_number', 'like', "%{$search}%") + ->orWhere('username', 'like', "%{$search}%"); + }); + } + + if ($acctType = $request->input('acct_type')) $query->where('acct_type', $acctType); + if ($request->input('active_only')) $query->where('active', true); + + $users = $query->paginate((int) $request->input('per_page', 25)); + + return response()->json(['success' => true, 'data' => $users]); + } + + public function show(Request $request) + { + $target = $request->input('target'); + $user = is_numeric($target) + ? User::find($target) + : User::where('hashkey', $target)->first(); + + if (!$user) return ResponseHelper::returnError('User not found', 404); + + if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewUserInfo)) { + return ResponseHelper::returnUnauthorized(); + } + + return response()->json(['success' => true, 'data' => $this->present($user)]); + } + + public function store(Request $request) + { + if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::CreateUser)) { + return ResponseHelper::returnUnauthorized(); + } + + $validator = Validator::make($request->all(), [ + 'name' => 'required|string|max:255', + 'mobile_number' => 'required|string|max:20|unique:users,mobile_number', + 'username' => 'nullable|string|max:100|unique:users,username', + 'password' => 'required|string|min:6', + 'acct_type' => 'required|string', + 'parentuid' => 'nullable|integer|exists:users,id', + ]); + + if ($validator->fails()) { + return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + } + + $acctType = UserTypes::tryFrom($request->input('acct_type')); + if (!$acctType) { + return ResponseHelper::returnError('Invalid account type', 422); + } + + $user = User::create([ + 'name' => $request->input('name'), + 'mobile_number' => $request->input('mobile_number'), + 'username' => $request->input('username'), + 'password' => Hash::make($request->input('password')), + 'acct_type' => $acctType, + 'parentuid' => $request->input('parentuid', Auth::id()), + 'hashkey' => hash('sha256', uniqid((string) now(), true)), + 'active' => true, + ]); + + return response()->json(['success' => true, 'data' => $this->present($user), 'message' => 'User created']); + } + + public function update(Request $request) + { + $target = $request->input('target'); + $user = User::where('hashkey', $target)->first(); + if (!$user) return ResponseHelper::returnError('User not found', 404); + + if (!UserPermissions::isActionPermitted($target, UserActions::ModifyUser)) { + return ResponseHelper::returnUnauthorized(); + } + + $allowedFields = ['name', 'username', 'acct_type', 'nickname', 'fullname']; + $data = $request->only($allowedFields); + if (isset($data['acct_type'])) { + $acctType = UserTypes::tryFrom($data['acct_type']); + if (!$acctType) return ResponseHelper::returnError('Invalid account type', 422); + $data['acct_type'] = $acctType; + } + + $user->update($data); + + return response()->json(['success' => true, 'data' => $this->present($user), 'message' => 'User updated']); + } + + public function setActive(Request $request) + { + $target = $request->input('target'); + $user = User::where('hashkey', $target)->first(); + if (!$user) return ResponseHelper::returnError('User not found', 404); + + $active = (bool) $request->input('active', true); + $action = $active ? UserActions::SetActiveUser : UserActions::SetInActiveUser; + + if (!UserPermissions::isActionPermitted($target, $action)) { + return ResponseHelper::returnUnauthorized(); + } + + $user->update(['active' => $active]); + + return response()->json(['success' => true, 'data' => $this->present($user)]); + } + + public function changePassword(Request $request) + { + $target = $request->input('target'); + $user = User::where('hashkey', $target)->first(); + if (!$user) return ResponseHelper::returnError('User not found', 404); + + if (!UserPermissions::isActionPermitted($target, UserActions::ChangeUserPassword)) { + return ResponseHelper::returnUnauthorized(); + } + + $validator = Validator::make($request->all(), [ + 'password' => 'required|string|min:6', + ]); + + if ($validator->fails()) { + return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + } + + $user->update(['password' => Hash::make($request->input('password'))]); + + // Force logout all sessions for this user + Redis::del("user_session:{$user->id}"); + + return response()->json(['success' => true, 'message' => 'Password changed']); + } + + public function forceLogout(Request $request) + { + $target = $request->input('target'); + $user = User::where('hashkey', $target)->first(); + if (!$user) return ResponseHelper::returnError('User not found', 404); + + if (!UserPermissions::isActionPermitted($target, UserActions::ForceLogoutUser)) { + return ResponseHelper::returnUnauthorized(); + } + + Redis::del("user_session:{$user->id}"); + + return response()->json(['success' => true, 'message' => 'User session cleared']); + } + + public function accountTypes() + { + $types = collect(UserTypes::cases())->map(fn ($t) => [ + 'value' => $t->value, + 'label' => ucwords(str_replace('_', ' ', $t->value)), + ]); + + return response()->json(['success' => true, 'data' => $types]); + } + + private function present(User $user): array + { + return [ + 'id' => $user->id, + 'hashkey' => $user->hashkey, + 'name' => $user->name, + 'fullname' => $user->fullname, + 'nickname' => $user->nickname, + 'username' => $user->username, + 'mobile_number' => $user->mobile_number, + 'acct_type' => $user->acct_type, + 'active' => (bool) $user->active, + 'parentuid' => $user->parentuid, + 'created_at' => $user->created_at, + 'updated_at' => $user->updated_at, + ]; + } +} diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php new file mode 100644 index 0000000..0359245 --- /dev/null +++ b/app/Http/Controllers/Auth/LoginController.php @@ -0,0 +1,184 @@ +inputs(['mobile_number', 'password']); + $keepalive = $request->input('keepalive'); + + if (!$credentials['mobile_number'] || !$credentials['password']) { + return Response::json([ + 'success' => false, + 'message' => 'Missing fields.', + ], 422); + } + + $candidates = self::phMobileVariants($credentials['mobile_number']); + + $user = User::whereIn('mobile_number', $candidates)->first(); + + if (!$user) { + return Response::json([ + 'success' => false, + 'message' => 'Account not found.', + ], 401); + } + + Log::info('Login attempt', [ + 'mobile_number' => $credentials['mobile_number'], + 'candidates' => $candidates, + 'user_found' => true, + 'active' => $user->active, + 'acct_type' => $user->acct_type->value ?? null, + ]); + + if (!$user->active) { + return Response::json([ + 'success' => false, + 'message' => 'Account is inactive. Please contact support.', + ], 401); + } + + if ($user && Hash::check($credentials['password'], $user->password)) { + + Auth::login($user); // or Auth::guard()->login($user) + + $current_userHashkey = $user->hashkey; + $sessionId = session()->getId(); + + + + + Redis::sadd("user_sessions:{$current_userHashkey}", $sessionId); + // $request->session()->regenerate(); + + Log::info('KeepAlive Value ' . $keepalive); + if ($keepalive === true || $keepalive === 'true') { + self::setSessiontoKeepAlive(); + } + + return Response::json([ + 'success' => true, + 'message' => 'Login successful', + ]); + } + + return Response::json([ + 'success' => false, + 'message' => 'Invalid credentials.', + ], 401); + + } + /** + * Set or extends current session auth
+ * This Function is Not Working JWT Automatically sets based on config file ttl + * @param string|bool $sessionid if false then get current sessionID + * @param int|bool $aliveinseconds if true then consider it forever + * @return null|bool // Time To Live TTL + */ + public static function setSessiontoKeepAlive(string|false $sessionId = false, int|bool $aliveinseconds = true) + { + $sessionId = $sessionId ?: session()->getId(); + if (!$sessionId) { + return false; + } + + if ($aliveinseconds === true) { + $aliveinseconds = 7889472; // 3 months + } + if ($aliveinseconds === false) { + return false; + } + + // The redis session driver stores the key under the Redis connection prefix. + // Cache::get/put uses a different prefix (cache store), so we use Redis::expire() + // directly to extend the TTL of the existing session key without reading its value. + $result = Redis::expire($sessionId, $aliveinseconds); + if (!$result) { + Log::warning('setSessiontoKeepAlive: session key not found in Redis, cannot extend TTL for: ' . $sessionId); + return false; + } + + $ttl = Redis::ttl($sessionId); + Log::info('extended session: ' . $sessionId . ' for ' . $aliveinseconds . 's, TTL: ' . $ttl); + return $ttl; + } + + /** + * Build all plausible stored forms of a Philippine mobile number so the + * caller can match whichever variant is in the DB. Variants returned: + * - 09XXXXXXXXX + * - 9XXXXXXXXX (no leading 0) + * - 639XXXXXXXXX + * - +639XXXXXXXXX + * plus the original raw input as a final fallback. + */ + public static function phMobileVariants(string $input): array + { + $digits = preg_replace('/\D+/', '', $input); + $core = null; // the 10-digit 9XXXXXXXXX portion + + if (preg_match('/^639(\d{9})$/', $digits, $m)) { + $core = '9' . $m[1]; + } elseif (preg_match('/^09(\d{9})$/', $digits, $m)) { + $core = '9' . $m[1]; + } elseif (preg_match('/^9(\d{9})$/', $digits, $m)) { + $core = '9' . $m[1]; + } + + $variants = [$input]; + if ($core) { + $variants[] = '0' . $core; + $variants[] = $core; + $variants[] = '63' . $core; + $variants[] = '+63' . $core; + } + + return array_values(array_unique($variants)); + } + + /** + * Back-compat shim: still used by UserModifyAdminPageController to + * canonicalize on save (admin edit). Picks 09XXXXXXXXX when possible. + */ + public static function normalizePhMobile(string $input): string + { + $digits = preg_replace('/\D+/', '', $input); + + if (preg_match('/^639(\d{9})$/', $digits, $m)) { + return '09' . $m[1]; + } + if (preg_match('/^9(\d{9})$/', $digits, $m)) { + return '09' . $m[1]; + } + if (preg_match('/^09\d{9}$/', $digits)) { + return $digits; + } + + return $input; + } + + public function extendcurrentSession() + { + $ttl = self::setSessiontoKeepAlive(); + + return Response::make('TTL is '.$ttl); + + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Barangay/BlotterController.php b/app/Http/Controllers/Barangay/BlotterController.php new file mode 100644 index 0000000..e4ae6b1 --- /dev/null +++ b/app/Http/Controllers/Barangay/BlotterController.php @@ -0,0 +1,140 @@ +acct_type, UserActions::ViewBlotters); + } + + private function checkWrite(): bool + { + return UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ProcessBlotter); + } + + public function index(Request $request) + { + if (!$this->checkRead()) return ResponseHelper::returnUnauthorized(); + + $query = Blotter::with(['assignedOfficer'])->orderByDesc('id'); + + if ($status = $request->input('status')) $query->where('status', $status); + if ($type = $request->input('incident_type')) $query->where('incident_type', $type); + if ($search = $request->input('search')) { + $query->where(function ($q) use ($search) { + $q->where('complainant_name', 'like', "%{$search}%") + ->orWhere('respondent_name', 'like', "%{$search}%") + ->orWhere('blotter_no', 'like', "%{$search}%"); + }); + } + + $blotters = $query->paginate((int) $request->input('per_page', 20)); + + return response()->json(['success' => true, 'data' => $blotters]); + } + + public function show(Request $request) + { + if (!$this->checkRead()) return ResponseHelper::returnUnauthorized(); + + $blotter = Blotter::with(['assignedOfficer', 'hearings.officer']) + ->where('hashkey', $request->input('target')) + ->first(); + + if (!$blotter) return ResponseHelper::returnError('Blotter not found', 404); + + return response()->json(['success' => true, 'data' => $blotter]); + } + + public function store(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $validator = Validator::make($request->all(), [ + 'complainant_name' => 'required|string|max:255', + 'complainant_contact' => 'nullable|string|max:30', + 'complainant_address' => 'nullable|string|max:500', + 'respondent_name' => 'required|string|max:255', + 'respondent_contact' => 'nullable|string|max:30', + 'respondent_address' => 'nullable|string|max:500', + 'incident_type' => 'required|in:AMICABLE,UNLAWFUL,MINOR,OTHER', + 'incident_date' => 'required|date', + 'incident_location' => 'nullable|string|max:500', + 'narrative' => 'required|string', + 'complainant_user_id' => 'nullable|integer|exists:users,id', + 'respondent_user_id' => 'nullable|integer|exists:users,id', + ]); + + if ($validator->fails()) { + return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + } + + $data = $validator->validated(); + $data['hashkey'] = hash('sha256', uniqid((string) now(), true)); + $data['blotter_no'] = Blotter::generateBlotterNo(); + $data['status'] = BlotterStatus::FILED; + $data['complaint_date'] = now()->toDateString(); + $data['filed_by'] = Auth::id(); + $data['is_active'] = true; + $data['created_by'] = Auth::id(); + $data['updated_by'] = Auth::id(); + + $blotter = Blotter::create($data); + + return response()->json(['success' => true, 'data' => $blotter, 'message' => 'Blotter filed successfully']); + } + + public function updateStatus(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $blotter = Blotter::where('hashkey', $request->input('target'))->first(); + if (!$blotter) return ResponseHelper::returnError('Blotter not found', 404); + + $newStatus = BlotterStatus::tryFrom($request->input('status')); + if (!$newStatus) return ResponseHelper::returnError('Invalid status', 422); + + $data = ['status' => $newStatus, 'updated_by' => Auth::id()]; + if ($request->input('resolution')) $data['resolution'] = $request->input('resolution'); + if ($request->input('endorsed_to')) $data['endorsed_to'] = $request->input('endorsed_to'); + if ($request->input('settlement_type')) $data['settlement_type'] = $request->input('settlement_type'); + + $blotter->update($data); + + return response()->json(['success' => true, 'data' => $blotter, 'message' => 'Status updated']); + } + + public function assignOfficer(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $blotter = Blotter::where('hashkey', $request->input('target'))->first(); + if (!$blotter) return ResponseHelper::returnError('Blotter not found', 404); + + $blotter->update([ + 'assigned_officer_id' => $request->input('officer_id'), + 'updated_by' => Auth::id(), + ]); + + return response()->json(['success' => true, 'data' => $blotter, 'message' => 'Officer assigned']); + } + + public function statusOptions() + { + $options = collect(BlotterStatus::cases())->map(fn ($s) => ['value' => $s->value, 'label' => $s->label()]); + return response()->json(['success' => true, 'data' => $options]); + } +} diff --git a/app/Http/Controllers/Barangay/BlotterHearingController.php b/app/Http/Controllers/Barangay/BlotterHearingController.php new file mode 100644 index 0000000..f94f55f --- /dev/null +++ b/app/Http/Controllers/Barangay/BlotterHearingController.php @@ -0,0 +1,77 @@ +acct_type, UserActions::ManageBlotterHearings); + } + + public function index(Request $request) + { + $blotter = Blotter::where('hashkey', $request->input('blotter'))->first(); + if (!$blotter) return ResponseHelper::returnError('Blotter not found', 404); + + $hearings = BlotterHearing::with('officer') + ->where('blotter_id', $blotter->id) + ->orderBy('hearing_date') + ->get(); + + return response()->json(['success' => true, 'data' => $hearings]); + } + + public function schedule(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $validator = Validator::make($request->all(), [ + 'blotter' => 'required|string', + 'hearing_date' => 'required|date', + 'officer_id' => 'nullable|integer|exists:users,id', + 'notes' => 'nullable|string', + ]); + + if ($validator->fails()) { + return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + } + + $blotter = Blotter::where('hashkey', $request->input('blotter'))->first(); + if (!$blotter) return ResponseHelper::returnError('Blotter not found', 404); + + $hearing = BlotterHearing::create([ + 'blotter_id' => $blotter->id, + 'hearing_date' => $request->input('hearing_date'), + 'status' => 'SCHEDULED', + 'officer_id' => $request->input('officer_id', $blotter->assigned_officer_id), + 'notes' => $request->input('notes'), + ]); + + return response()->json(['success' => true, 'data' => $hearing, 'message' => 'Hearing scheduled']); + } + + public function update(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $hearing = BlotterHearing::find($request->input('hearing_id')); + if (!$hearing) return ResponseHelper::returnError('Hearing not found', 404); + + $data = $request->only(['status', 'notes', 'resolution', 'next_hearing_date']); + $hearing->update($data); + + return response()->json(['success' => true, 'data' => $hearing, 'message' => 'Hearing updated']); + } +} diff --git a/app/Http/Controllers/Barangay/BudgetController.php b/app/Http/Controllers/Barangay/BudgetController.php new file mode 100644 index 0000000..8516e44 --- /dev/null +++ b/app/Http/Controllers/Barangay/BudgetController.php @@ -0,0 +1,126 @@ +acct_type, UserActions::ViewBarangayBudget); + } + + private function checkWrite(): bool + { + return UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageBarangayBudget); + } + + public function index(Request $request) + { + if (!$this->checkRead()) return ResponseHelper::returnUnauthorized(); + + $query = BarangayBudget::with('encodedBy')->orderByDesc('date'); + + if ($year = $request->input('year')) $query->byYear((int) $year); + if ($category = $request->input('category')) $query->where('category', $category); + + $entries = $query->paginate((int) $request->input('per_page', 30)); + + return response()->json(['success' => true, 'data' => $entries]); + } + + public function summary(Request $request) + { + if (!$this->checkRead()) return ResponseHelper::returnUnauthorized(); + + $year = (int) $request->input('year', date('Y')); + + $income = BarangayBudget::byYear($year)->income()->sum('amount'); + $expense = BarangayBudget::byYear($year)->expense()->sum('amount'); + + $bySource = BarangayBudget::byYear($year) + ->selectRaw('category, source, SUM(amount) as total') + ->groupBy('category', 'source') + ->orderBy('category') + ->orderByDesc('total') + ->get(); + + return response()->json([ + 'success' => true, + 'data' => [ + 'year' => $year, + 'income' => $income, + 'expense' => $expense, + 'balance' => $income - $expense, + 'by_source' => $bySource, + ], + ]); + } + + public function store(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $validator = Validator::make($request->all(), [ + 'fiscal_year' => 'required|integer|min:2020|max:2100', + 'category' => 'required|in:INCOME,EXPENSE', + 'source' => 'required|string|max:255', + 'amount' => 'required|numeric|min:0.01', + 'description' => 'nullable|string|max:500', + 'date' => 'required|date', + 'reference' => 'nullable|string|max:100', + ]); + + if ($validator->fails()) { + return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + } + + $data = $validator->validated(); + $data['hashkey'] = hash('sha256', uniqid((string) now(), true)); + $data['encoded_by'] = Auth::id(); + + $entry = BarangayBudget::create($data); + + return response()->json(['success' => true, 'data' => $entry, 'message' => 'Budget entry recorded']); + } + + public function update(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $entry = BarangayBudget::where('hashkey', $request->input('target'))->first(); + if (!$entry) return ResponseHelper::returnError('Entry not found', 404); + + $data = $request->only(['source', 'amount', 'description', 'date', 'reference', 'category']); + $entry->update($data); + + return response()->json(['success' => true, 'data' => $entry, 'message' => 'Budget entry updated']); + } + + public function destroy(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $entry = BarangayBudget::where('hashkey', $request->input('target'))->first(); + if (!$entry) return ResponseHelper::returnError('Entry not found', 404); + + $entry->delete(); + + return response()->json(['success' => true, 'message' => 'Budget entry deleted']); + } + + public function fiscalYears() + { + $years = BarangayBudget::distinct()->orderByDesc('fiscal_year')->pluck('fiscal_year'); + return response()->json(['success' => true, 'data' => $years]); + } +} diff --git a/app/Http/Controllers/Barangay/DocumentRequestController.php b/app/Http/Controllers/Barangay/DocumentRequestController.php new file mode 100644 index 0000000..39b4d2c --- /dev/null +++ b/app/Http/Controllers/Barangay/DocumentRequestController.php @@ -0,0 +1,210 @@ +acct_type, UserActions::ViewDocumentRequests); + } + + private function checkWrite(): bool + { + return UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ProcessDocumentRequest); + } + + public function index(Request $request) + { + if (!$this->checkRead()) return ResponseHelper::returnUnauthorized(); + + $query = DocumentRequest::with(['requestType', 'resident', 'processedBy'])->orderByDesc('id'); + + if ($status = $request->input('status')) $query->where('status', $status); + if ($payStatus = $request->input('payment_status')) $query->where('payment_status', $payStatus); + if ($typeId = $request->input('request_type_id')) $query->where('request_type_id', $typeId); + if ($search = $request->input('search')) { + $query->where('request_no', 'like', "%{$search}%"); + } + + $requests = $query->paginate((int) $request->input('per_page', 20)); + + return response()->json(['success' => true, 'data' => $requests]); + } + + public function myRequests(Request $request) + { + $requests = DocumentRequest::with('requestType') + ->where('resident_user_id', Auth::id()) + ->orderByDesc('id') + ->get(); + + return response()->json(['success' => true, 'data' => $requests]); + } + + public function show(Request $request) + { + if (!$this->checkRead()) return ResponseHelper::returnUnauthorized(); + + $doc = DocumentRequest::with(['requestType', 'resident', 'processedBy', 'payments']) + ->where('hashkey', $request->input('target')) + ->first(); + + if (!$doc) return ResponseHelper::returnError('Request not found', 404); + + return response()->json(['success' => true, 'data' => $doc]); + } + + public function store(Request $request) + { + $validator = Validator::make($request->all(), [ + 'request_type_id' => 'required|integer|exists:barangay_request_types,id', + 'purpose' => 'required|string|max:500', + ]); + + if ($validator->fails()) { + return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + } + + $reqType = RequestType::findOrFail($request->input('request_type_id')); + + $doc = DocumentRequest::create([ + 'hashkey' => hash('sha256', uniqid((string) now(), true)), + 'request_no' => DocumentRequest::generateRequestNo(), + 'resident_user_id' => Auth::id(), + 'request_type_id' => $reqType->id, + 'purpose' => $request->input('purpose'), + 'fee_amount' => $reqType->base_fee, + 'payment_status' => PaymentStatus::PENDING, + 'status' => $reqType->base_fee > 0 ? DocumentStatus::PENDING_PAYMENT : DocumentStatus::PAID, + 'requested_by' => Auth::id(), + ]); + + return response()->json(['success' => true, 'data' => $doc, 'message' => 'Document request submitted']); + } + + public function updateStatus(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $doc = DocumentRequest::where('hashkey', $request->input('target'))->first(); + if (!$doc) return ResponseHelper::returnError('Request not found', 404); + + $newStatus = DocumentStatus::tryFrom($request->input('status')); + if (!$newStatus) return ResponseHelper::returnError('Invalid status', 422); + + $data = [ + 'status' => $newStatus, + 'processed_by' => Auth::id(), + ]; + + if ($newStatus === DocumentStatus::CLAIMED) { + $data['claimed_at'] = now(); + } + + if ($request->input('notes')) $data['notes'] = $request->input('notes'); + + $doc->update($data); + + return response()->json(['success' => true, 'data' => $doc, 'message' => 'Status updated to ' . $newStatus->label()]); + } + + public function confirmPayment(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $doc = DocumentRequest::where('hashkey', $request->input('target'))->first(); + if (!$doc) return ResponseHelper::returnError('Request not found', 404); + + $validator = Validator::make($request->all(), [ + 'method' => 'required|in:CASH,GCASH,QRPH,BANK,WAIVED', + 'reference' => 'nullable|string|max:100', + 'amount' => 'nullable|numeric|min:0', + ]); + + if ($validator->fails()) { + return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + } + + $method = $request->input('method'); + $amount = $request->input('amount', $doc->fee_amount); + + RequestPayment::create([ + 'request_id' => $doc->id, + 'amount' => $amount, + 'method' => $method, + 'reference' => $request->input('reference'), + 'paid_at' => now(), + 'verified_by' => Auth::id(), + ]); + + $doc->update([ + 'payment_status' => PaymentStatus::PAID, + 'payment_method' => $method, + 'payment_ref' => $request->input('reference'), + 'status' => DocumentStatus::PROCESSING, + 'processed_by' => Auth::id(), + ]); + + return response()->json(['success' => true, 'data' => $doc, 'message' => 'Payment confirmed']); + } + + public function markReady(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $doc = DocumentRequest::where('hashkey', $request->input('target'))->first(); + if (!$doc) return ResponseHelper::returnError('Request not found', 404); + + $doc->update(['status' => DocumentStatus::READY, 'processed_by' => Auth::id()]); + + return response()->json(['success' => true, 'data' => $doc, 'message' => 'Marked as ready for pickup']); + } + + public function markClaimed(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $doc = DocumentRequest::where('hashkey', $request->input('target'))->first(); + if (!$doc) return ResponseHelper::returnError('Request not found', 404); + + $doc->update([ + 'status' => DocumentStatus::CLAIMED, + 'claimed_at' => now(), + ]); + + return response()->json(['success' => true, 'data' => $doc, 'message' => 'Document marked as claimed']); + } + + public function cancel(Request $request) + { + $doc = DocumentRequest::where('hashkey', $request->input('target'))->first(); + if (!$doc) return ResponseHelper::returnError('Request not found', 404); + + if ($doc->resident_user_id !== Auth::id() && !$this->checkWrite()) { + return ResponseHelper::returnUnauthorized(); + } + + if (in_array($doc->status, [DocumentStatus::CLAIMED, DocumentStatus::CANCELLED])) { + return ResponseHelper::returnError('Cannot cancel this request', 422); + } + + $doc->update(['status' => DocumentStatus::CANCELLED]); + + return response()->json(['success' => true, 'message' => 'Request cancelled']); + } +} diff --git a/app/Http/Controllers/Barangay/HouseholdController.php b/app/Http/Controllers/Barangay/HouseholdController.php new file mode 100644 index 0000000..0718a5b --- /dev/null +++ b/app/Http/Controllers/Barangay/HouseholdController.php @@ -0,0 +1,164 @@ +acct_type, UserActions::ViewResidents); + } + + private function checkWrite(): bool + { + return UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageResidents); + } + + public function index(Request $request) + { + if (!$this->checkRead()) return ResponseHelper::returnUnauthorized(); + + $query = Household::with(['head', 'members.resident'])->orderByDesc('id'); + + if ($purok = $request->input('purok')) $query->where('purok', $purok); + if ($request->input('active_only')) $query->active(); + + $households = $query->paginate((int) $request->input('per_page', 20)); + + return response()->json(['success' => true, 'data' => $households]); + } + + public function show(Request $request) + { + if (!$this->checkRead()) return ResponseHelper::returnUnauthorized(); + + $household = Household::with(['head', 'members.resident']) + ->where('hashkey', $request->input('target')) + ->first(); + + if (!$household) return ResponseHelper::returnError('Household not found', 404); + + return response()->json(['success' => true, 'data' => $household]); + } + + public function store(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $validator = Validator::make($request->all(), [ + 'head_resident_id' => 'required|integer|exists:barangay_residents,id', + 'address' => 'required|string|max:500', + 'purok' => 'nullable|string|max:100', + 'barangay' => 'nullable|string|max:100', + 'city' => 'nullable|string|max:100', + 'province' => 'nullable|string|max:100', + 'ownership_type' => 'nullable|in:OWNED,RENTED,SHARED', + 'monthly_rental' => 'nullable|numeric|min:0', + 'has_electricity' => 'nullable|boolean', + 'has_water' => 'nullable|boolean', + 'housing_material' => 'nullable|string|max:100', + ]); + + if ($validator->fails()) { + return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + } + + $data = $validator->validated(); + $year = date('Y'); + $count = Household::whereYear('created_at', $year)->count() + 1; + $data['household_no'] = sprintf('HH-%s-%04d', $year, $count); + $data['hashkey'] = hash('sha256', uniqid((string) now(), true)); + $data['member_count'] = 1; + $data['is_active'] = true; + $data['created_by'] = Auth::id(); + $data['updated_by'] = Auth::id(); + + $household = Household::create($data); + + // Add head as first member + HouseholdMember::create([ + 'household_id' => $household->id, + 'resident_id' => $data['head_resident_id'], + 'relationship_to_head' => 'HEAD', + 'is_active' => true, + ]); + + return response()->json(['success' => true, 'data' => $household, 'message' => 'Household created']); + } + + public function addMember(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $validator = Validator::make($request->all(), [ + 'target' => 'required|string', + 'resident_id' => 'required|integer|exists:barangay_residents,id', + 'relationship_to_head' => 'required|string|max:100', + ]); + + if ($validator->fails()) { + return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + } + + $household = Household::where('hashkey', $request->input('target'))->first(); + if (!$household) return ResponseHelper::returnError('Household not found', 404); + + $existing = HouseholdMember::where('household_id', $household->id) + ->where('resident_id', $request->input('resident_id')) + ->first(); + if ($existing) return ResponseHelper::returnError('Resident is already a member', 422); + + $member = HouseholdMember::create([ + 'household_id' => $household->id, + 'resident_id' => $request->input('resident_id'), + 'relationship_to_head' => $request->input('relationship_to_head'), + 'is_active' => true, + ]); + + $household->increment('member_count'); + + return response()->json(['success' => true, 'data' => $member, 'message' => 'Member added']); + } + + public function removeMember(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $household = Household::where('hashkey', $request->input('target'))->first(); + if (!$household) return ResponseHelper::returnError('Household not found', 404); + + $deleted = HouseholdMember::where('household_id', $household->id) + ->where('resident_id', $request->input('resident_id')) + ->delete(); + + if ($deleted) $household->decrement('member_count'); + + return response()->json(['success' => true, 'message' => 'Member removed']); + } + + public function update(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $household = Household::where('hashkey', $request->input('target'))->first(); + if (!$household) return ResponseHelper::returnError('Household not found', 404); + + $data = $request->except(['target', 'hashkey', 'household_no', 'created_by']); + $data['updated_by'] = Auth::id(); + $household->update($data); + + return response()->json(['success' => true, 'data' => $household, 'message' => 'Household updated']); + } +} diff --git a/app/Http/Controllers/Barangay/ProjectController.php b/app/Http/Controllers/Barangay/ProjectController.php new file mode 100644 index 0000000..1f0b6f1 --- /dev/null +++ b/app/Http/Controllers/Barangay/ProjectController.php @@ -0,0 +1,135 @@ +acct_type, UserActions::ViewBarangayProjects); + } + + private function checkWrite(): bool + { + return UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageBarangayProjects); + } + + public function index(Request $request) + { + if (!$this->checkRead()) return ResponseHelper::returnUnauthorized(); + + $query = BarangayProject::orderByDesc('id'); + + if ($status = $request->input('status')) $query->where('status', $status); + if ($type = $request->input('type')) $query->where('type', $type); + if ($year = $request->input('year')) { + $query->whereYear('start_date', $year); + } + + $projects = $query->paginate((int) $request->input('per_page', 20)); + + return response()->json(['success' => true, 'data' => $projects]); + } + + public function show(Request $request) + { + if (!$this->checkRead()) return ResponseHelper::returnUnauthorized(); + + $project = BarangayProject::where('hashkey', $request->input('target'))->first(); + if (!$project) return ResponseHelper::returnError('Project not found', 404); + + return response()->json(['success' => true, 'data' => $project]); + } + + public function store(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $validator = Validator::make($request->all(), [ + 'project_name' => 'required|string|max:500', + 'description' => 'nullable|string', + 'type' => 'required|in:INFRASTRUCTURE,LIVELIHOOD,HEALTH,EDUCATION,ENVIRONMENT,OTHERS', + 'budget' => 'required|numeric|min:0', + 'fund_source' => 'required|in:GENERAL_FUND,SK,PROVINCE,NATIONAL,OTHERS', + 'start_date' => 'required|date', + 'end_date' => 'nullable|date|after:start_date', + 'implementing_office' => 'nullable|string|max:255', + 'contractor' => 'nullable|string|max:255', + 'location' => 'nullable|string|max:500', + 'beneficiaries_count' => 'nullable|integer|min:0', + ]); + + if ($validator->fails()) { + return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + } + + $data = $validator->validated(); + $data['hashkey'] = hash('sha256', uniqid((string) now(), true)); + $data['status'] = 'PLANNED'; + $data['created_by'] = Auth::id(); + $data['updated_by'] = Auth::id(); + + $project = BarangayProject::create($data); + + return response()->json(['success' => true, 'data' => $project, 'message' => 'Project created']); + } + + public function update(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $project = BarangayProject::where('hashkey', $request->input('target'))->first(); + if (!$project) return ResponseHelper::returnError('Project not found', 404); + + $data = $request->except(['target', 'hashkey', 'created_by']); + $data['updated_by'] = Auth::id(); + $project->update($data); + + return response()->json(['success' => true, 'data' => $project, 'message' => 'Project updated']); + } + + public function updateStatus(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $project = BarangayProject::where('hashkey', $request->input('target'))->first(); + if (!$project) return ResponseHelper::returnError('Project not found', 404); + + $validStatuses = ['PLANNED', 'ONGOING', 'COMPLETED', 'SUSPENDED', 'CANCELLED']; + $status = $request->input('status'); + if (!in_array($status, $validStatuses)) { + return ResponseHelper::returnError('Invalid status', 422); + } + + $project->update(['status' => $status, 'updated_by' => Auth::id()]); + + return response()->json(['success' => true, 'data' => $project, 'message' => "Project status set to {$status}"]); + } + + public function summary() + { + if (!$this->checkRead()) return ResponseHelper::returnUnauthorized(); + + $summary = [ + 'total' => BarangayProject::count(), + 'planned' => BarangayProject::where('status', 'PLANNED')->count(), + 'ongoing' => BarangayProject::where('status', 'ONGOING')->count(), + 'completed' => BarangayProject::where('status', 'COMPLETED')->count(), + 'total_budget' => BarangayProject::sum('budget'), + 'by_type' => BarangayProject::selectRaw('type, count(*) as count, sum(budget) as budget') + ->groupBy('type')->get(), + ]; + + return response()->json(['success' => true, 'data' => $summary]); + } +} diff --git a/app/Http/Controllers/Barangay/RequestTypeController.php b/app/Http/Controllers/Barangay/RequestTypeController.php new file mode 100644 index 0000000..89af147 --- /dev/null +++ b/app/Http/Controllers/Barangay/RequestTypeController.php @@ -0,0 +1,95 @@ +acct_type, UserActions::ManageRequestTypes); + } + + public function index() + { + $types = RequestType::orderBy('name')->get(); + return response()->json(['success' => true, 'data' => $types]); + } + + public function active() + { + $types = RequestType::active()->orderBy('name')->get(); + return response()->json(['success' => true, 'data' => $types]); + } + + public function store(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $validator = Validator::make($request->all(), [ + 'name' => 'required|string|max:255', + 'code' => 'required|string|max:50|unique:barangay_request_types,code', + 'description' => 'nullable|string', + 'base_fee' => 'required|numeric|min:0', + 'processing_days' => 'required|integer|min:1', + 'requires_clearance' => 'nullable|boolean', + ]); + + if ($validator->fails()) { + return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + } + + $data = $validator->validated(); + $data['is_active'] = true; + $data['code'] = strtoupper($data['code']); + + $type = RequestType::create($data); + + return response()->json(['success' => true, 'data' => $type, 'message' => 'Request type created']); + } + + public function update(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $type = RequestType::where('code', $request->input('target'))->first(); + if (!$type) return ResponseHelper::returnError('Request type not found', 404); + + $validator = Validator::make($request->all(), [ + 'name' => 'nullable|string|max:255', + 'description' => 'nullable|string', + 'base_fee' => 'nullable|numeric|min:0', + 'processing_days' => 'nullable|integer|min:1', + 'requires_clearance' => 'nullable|boolean', + ]); + + if ($validator->fails()) { + return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + } + + $type->update($validator->validated()); + + return response()->json(['success' => true, 'data' => $type, 'message' => 'Request type updated']); + } + + public function toggleActive(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $type = RequestType::where('code', $request->input('target'))->first(); + if (!$type) return ResponseHelper::returnError('Request type not found', 404); + + $type->update(['is_active' => !$type->is_active]); + + return response()->json(['success' => true, 'data' => $type]); + } +} diff --git a/app/Http/Controllers/Barangay/ResidentController.php b/app/Http/Controllers/Barangay/ResidentController.php new file mode 100644 index 0000000..6d0ab39 --- /dev/null +++ b/app/Http/Controllers/Barangay/ResidentController.php @@ -0,0 +1,159 @@ +acct_type, UserActions::ViewResidents); + } + + private function checkWrite(): bool + { + return UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageResidents); + } + + public function index(Request $request) + { + if (!$this->checkRead()) return ResponseHelper::returnUnauthorized(); + + $query = Resident::with('user')->orderByDesc('id'); + + if ($search = $request->input('search')) { + $query->where(function ($q) use ($search) { + $q->where('firstname', 'like', "%{$search}%") + ->orWhere('lastname', 'like', "%{$search}%") + ->orWhere('middlename', 'like', "%{$search}%"); + }); + } + + if ($purok = $request->input('purok')) $query->where('purok', $purok); + if ($request->input('active_only')) $query->active(); + + $residents = $query->paginate((int) $request->input('per_page', 20)); + + return response()->json(['success' => true, 'data' => $residents]); + } + + public function show(Request $request) + { + if (!$this->checkRead()) return ResponseHelper::returnUnauthorized(); + + $resident = Resident::with(['user', 'householdMemberships.household']) + ->where('hashkey', $request->input('target')) + ->first(); + + if (!$resident) return ResponseHelper::returnError('Resident not found', 404); + + return response()->json(['success' => true, 'data' => $resident]); + } + + public function store(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $validator = Validator::make($request->all(), [ + 'firstname' => 'required|string|max:100', + 'lastname' => 'required|string|max:100', + 'middlename' => 'nullable|string|max:100', + 'suffix' => 'nullable|string|max:20', + 'dob' => 'required|date', + 'birthplace' => 'nullable|string|max:255', + 'gender' => 'required|in:MALE,FEMALE,OTHER', + 'civil_status' => 'required|in:SINGLE,MARRIED,WIDOWED,SEPARATED,ANNULLED', + 'citizenship' => 'nullable|string|max:100', + 'religion' => 'nullable|string|max:100', + 'occupation' => 'nullable|string|max:255', + 'monthly_income' => 'nullable|numeric|min:0', + 'blood_type' => 'nullable|string|max:5', + 'voter_status' => 'nullable|boolean', + 'head_of_household' => 'nullable|boolean', + 'purok' => 'nullable|string|max:100', + 'street' => 'nullable|string|max:255', + 'barangay' => 'nullable|string|max:100', + 'city' => 'nullable|string|max:100', + 'province' => 'nullable|string|max:100', + 'region' => 'nullable|string|max:100', + 'philhealth_id' => 'nullable|string|max:50', + 'sss_id' => 'nullable|string|max:50', + 'gsis_id' => 'nullable|string|max:50', + 'tin' => 'nullable|string|max:50', + 'emergency_contact_name' => 'nullable|string|max:255', + 'emergency_contact_phone' => 'nullable|string|max:30', + 'emergency_contact_address' => 'nullable|string|max:255', + 'user_id' => 'nullable|integer|exists:users,id', + ]); + + if ($validator->fails()) { + return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + } + + $data = $validator->validated(); + $data['hashkey'] = hash('sha256', uniqid((string) now(), true)); + $data['is_active'] = true; + $data['created_by'] = Auth::id(); + $data['updated_by'] = Auth::id(); + + $resident = Resident::create($data); + + return response()->json(['success' => true, 'data' => $resident, 'message' => 'Resident created']); + } + + public function update(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $resident = Resident::where('hashkey', $request->input('target'))->first(); + if (!$resident) return ResponseHelper::returnError('Resident not found', 404); + + $data = $request->except(['target', 'hashkey', 'created_by']); + $data['updated_by'] = Auth::id(); + $resident->update($data); + + return response()->json(['success' => true, 'data' => $resident, 'message' => 'Resident updated']); + } + + public function setActive(Request $request) + { + if (!$this->checkWrite()) return ResponseHelper::returnUnauthorized(); + + $resident = Resident::where('hashkey', $request->input('target'))->first(); + if (!$resident) return ResponseHelper::returnError('Resident not found', 404); + + $resident->update(['is_active' => (bool) $request->input('active', true)]); + + return response()->json(['success' => true, 'data' => $resident]); + } + + public function search(Request $request) + { + if (!$this->checkRead()) return ResponseHelper::returnUnauthorized(); + + $term = $request->input('q', ''); + $residents = Resident::where(function ($q) use ($term) { + $q->where('firstname', 'like', "%{$term}%") + ->orWhere('lastname', 'like', "%{$term}%") + ->orWhere('middlename', 'like', "%{$term}%"); + })->active()->limit(20)->get(['id', 'hashkey', 'firstname', 'middlename', 'lastname', 'suffix', 'purok', 'dob']); + + return response()->json(['success' => true, 'data' => $residents]); + } + + public function puroks() + { + $puroks = Resident::distinct()->orderBy('purok')->pluck('purok')->filter()->values(); + return response()->json(['success' => true, 'data' => $puroks]); + } +} diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php new file mode 100644 index 0000000..bc1423c --- /dev/null +++ b/app/Http/Controllers/ChapterController.php @@ -0,0 +1,241 @@ + ['region'], + 'region' => ['province'], + 'province' => ['city', 'municipal'], + 'city' => ['barangay'], + 'municipal' => ['barangay'], + 'barangay' => ['purok'], + 'purok' => [], + ]; + + private function isAdmin(): bool + { + return UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageChapterMembers); + } + + public function hierarchy(Request $request) + { + $chapterId = $request->input('chapter_id'); + + if (!$chapterId) { + $national = Chapter::where('level', 'national')->where('location_key', 'philippines')->first(); + if (!$national) { + return response()->json(['chapters' => [], 'current' => null, 'breadcrumb' => []]); + } + $chapters = $national->children() + ->where('is_active', true) + ->withCount('activeMembers') + ->with('leaders.user') + ->get() + ->map(fn ($c) => $this->formatChapter($c)) + ->values(); + + return response()->json(['chapters' => $chapters, 'current' => null, 'breadcrumb' => []]); + } + + $current = Chapter::withCount('activeMembers')->with('leaders.user')->find($chapterId); + if (!$current) return ResponseHelper::returnError('Chapter not found', 404); + + $chapters = $current->children() + ->where('is_active', true) + ->withCount('activeMembers') + ->with('leaders.user') + ->get() + ->map(fn ($c) => $this->formatChapter($c)) + ->values(); + + return response()->json([ + 'chapters' => $chapters, + 'current' => $this->formatChapter($current), + 'breadcrumb' => $this->buildBreadcrumb($current), + ]); + } + + public function mapData(Request $request) + { + $level = $request->input('level', 'region'); + $parentId = $request->input('parent_id'); + + $query = Chapter::where('level', $level)->where('is_active', true)->withCount('activeMembers'); + if ($parentId) $query->where('parent_id', $parentId); + + $chapters = $query->get() + ->filter(fn ($c) => $c->lat && $c->lng) + ->map(fn ($c) => [ + 'id' => $c->id, + 'name' => $c->name, + 'level' => $c->level, + 'lat' => $c->lat, + 'lng' => $c->lng, + 'count' => $c->active_members_count, + ]) + ->values(); + + return response()->json(['chapters' => $chapters]); + } + + public function members(Request $request) + { + $chapterId = $request->input('chapter_id'); + if (!$chapterId) return ResponseHelper::returnError('chapter_id required', 400); + + $members = ChapterMember::with('user') + ->where('chapter_id', $chapterId) + ->where('is_active', true) + ->get() + ->map(fn ($cm) => [ + 'hashkey' => $cm->hashkey, + 'name' => $cm->user?->fullname ?? $cm->user?->name, + 'position' => $cm->position, + 'photo' => $cm->user?->photourl ?? null, + 'is_manual' => (bool) $cm->is_manual_override, + ]); + + return response()->json(['members' => $members]); + } + + public function assignMember(Request $request) + { + if (!$this->isAdmin()) return ResponseHelper::returnUnauthorized(); + + $validator = Validator::make($request->all(), [ + 'user_hashkey' => 'required|string', + 'chapter_id' => 'required|integer', + 'position' => 'nullable|string|max:100', + ]); + + if ($validator->fails()) return ResponseHelper::returnError('Validation failed', 422, $validator->errors()); + + $user = User::where('hashkey', $request->input('user_hashkey'))->first(); + if (!$user) return ResponseHelper::returnError('User not found', 404); + + $chapter = Chapter::find($request->input('chapter_id')); + if (!$chapter) return ResponseHelper::returnError('Chapter not found', 404); + + $existing = ChapterMember::where('user_id', $user->id)->where('chapter_id', $chapter->id)->first(); + + if ($existing) { + $existing->update([ + 'position' => $request->input('position'), + 'is_manual_override' => true, + 'assigned_by' => Auth::id(), + 'assigned_at' => now(), + 'updated_by' => Auth::id(), + ]); + } else { + ChapterMember::create([ + 'hashkey' => (string) Str::uuid(), + 'user_id' => $user->id, + 'chapter_id' => $chapter->id, + 'position' => $request->input('position'), + 'is_manual_override' => true, + 'is_active' => true, + 'assigned_by' => Auth::id(), + 'assigned_at' => now(), + 'created_by' => Auth::id(), + ]); + } + + return response()->json(['success' => true]); + } + + public function removeMember(Request $request) + { + if (!$this->isAdmin()) return ResponseHelper::returnUnauthorized(); + + $member = ChapterMember::where('hashkey', $request->input('hashkey'))->first(); + if (!$member) return ResponseHelper::returnError('Member assignment not found', 404); + + $member->update(['is_active' => false, 'updated_by' => Auth::id()]); + + return response()->json(['success' => true]); + } + + public function positions() + { + $raw = SystemSetting::getValue('chapter_positions'); + $positions = is_string($raw) ? (json_decode($raw, true) ?? []) : []; + if (empty($positions)) { + $positions = ['Punong Barangay', 'Kagawad', 'Secretary', 'Treasurer', 'SK Chairperson', 'SK Councilor', 'Tanod', 'BHW', 'Daycare Worker', 'Staff', 'Member']; + } + return response()->json(['positions' => $positions]); + } + + public function createChapter(Request $request) + { + if (!$this->isAdmin()) return ResponseHelper::returnUnauthorized(); + + $validator = Validator::make($request->all(), [ + 'name' => 'required|string|max:255', + 'level' => 'required|in:national,region,province,city,municipal,barangay,purok', + 'parent_id' => 'nullable|integer|exists:chapters,id', + 'location_key' => 'nullable|string|max:255', + 'lat' => 'nullable|numeric', + 'lng' => 'nullable|numeric', + ]); + + if ($validator->fails()) { + return response()->json(['success' => false, 'errors' => $validator->errors()], 422); + } + + $data = $validator->validated(); + $data['hashkey'] = hash('sha256', uniqid((string) now(), true)); + $data['is_active'] = true; + $data['created_by'] = Auth::id(); + $data['updated_by'] = Auth::id(); + $data['location_key'] = strtolower(trim($data['location_key'] ?? $data['name'])); + + $chapter = Chapter::create($data); + + return response()->json(['success' => true, 'data' => $this->formatChapter($chapter), 'message' => 'Chapter created']); + } + + private function formatChapter(Chapter $c): array + { + return [ + 'id' => $c->id, + 'hashkey' => $c->hashkey, + 'name' => $c->name, + 'level' => $c->level, + 'parent_id' => $c->parent_id, + 'location_key' => $c->location_key, + 'lat' => $c->lat, + 'lng' => $c->lng, + 'is_active' => $c->is_active, + 'member_count' => $c->active_members_count ?? 0, + 'child_levels' => self::CHILD_LEVELS[$c->level] ?? [], + ]; + } + + private function buildBreadcrumb(Chapter $chapter): array + { + $crumbs = []; + $current = $chapter; + while ($current) { + array_unshift($crumbs, ['id' => $current->id, 'name' => $current->name, 'level' => $current->level]); + $current = $current->parent; + } + return $crumbs; + } +} diff --git a/app/Http/Controllers/FileController.php b/app/Http/Controllers/FileController.php new file mode 100644 index 0000000..e8c6d25 --- /dev/null +++ b/app/Http/Controllers/FileController.php @@ -0,0 +1,223 @@ + $threshold; + } + + private static function insertFileContentPostgresPDO(array $data) + { + $pdo = DB::getPdo(); + $stmt = $pdo->prepare(' + INSERT INTO file_content + (hashkey, filehash, titlename, description, size_in_bytes, content, filelocation, created_by, updated_by, details, created_at, updated_at) + VALUES + (:hashkey, :filehash, :titlename, :description, :size_in_bytes, :content, :filelocation, :created_by, :updated_by, :details, :created_at, :updated_at) + '); + $now = now()->toDateTimeString(); + $stmt->bindParam(':hashkey', $data['hashkey']); + $stmt->bindParam(':filehash', $data['filehash']); + $stmt->bindParam(':titlename', $data['titlename']); + $stmt->bindParam(':description', $data['description']); + $stmt->bindParam(':size_in_bytes', $data['size_in_bytes']); + $stmt->bindParam(':content', $data['content'], PDO::PARAM_LOB); + $stmt->bindParam(':filelocation', $data['filelocation']); + $stmt->bindParam(':created_by', $data['created_by']); + $stmt->bindParam(':updated_by', $data['updated_by']); + $detailsJson = json_encode($data['details']); + $stmt->bindParam(':details', $detailsJson); + $stmt->bindParam(':created_at', $now); + $stmt->bindParam(':updated_at', $now); + $stmt->execute(); + $id = DB::getPdo()->lastInsertId(); + return FileContent::find($id); + } + + private static function insertFileContentSql(array $data) + { + $now = Carbon::now(); + $data['content'] = base64_encode($data['content']); + $data['details'] = json_encode($data['details']); + $data['created_at'] = $now; + $data['updated_at'] = $now; + return FileContent::create($data); + } + + private static function insertFileContent(array $data) + { + $driver = DB::connection()->getDriverName(); + if ($driver === 'pgsql') return self::insertFileContentPostgresPDO($data); + if ($driver === 'mysql') return self::insertFileContentSql($data); + throw new \RuntimeException("Unsupported database driver: {$driver}"); + } + + private static function uploadFileContent(UploadedFile|string $fileData, string $title, ?string $description = null, ?array $details = []) + { + if ($fileData instanceof UploadedFile) { + $fileHash = hash_file('sha256', $fileData->getRealPath()); + $fileSize = $fileData->getSize(); + $fileContent = File::get($fileData->getRealPath()); + $path = $fileData->storeAs('files', $fileHash); + } elseif (is_string($fileData) && file_exists($fileData)) { + $fileHash = hash_file('sha256', $fileData); + $fileSize = filesize($fileData); + $fileContent = file_get_contents($fileData); + $path = Storage::put("files/{$fileHash}", $fileContent); + } elseif (self::isLikelyBinary($fileData)) { + $fileHash = hash('sha256', $fileData); + $fileSize = strlen($fileData); + $fileContent = $fileData; + $path = Storage::put("files/{$fileHash}", $fileContent); + } else { + throw new \InvalidArgumentException('Invalid file data provided.'); + } + + $existing = FileContent::where('filehash', $fileHash)->first(); + if ($existing) return $existing; + + $hashKey = hash('sha256', uniqid((string) now(), true)); + $finfo = new \finfo(FILEINFO_MIME_TYPE); + $mimetype = $finfo->buffer($fileContent); + + return self::insertFileContent([ + 'hashkey' => $hashKey, + 'filehash' => $fileHash, + 'titlename' => $title, + 'description' => $description, + 'size_in_bytes' => $fileSize, + 'content' => $fileContent, + 'filelocation' => $path, + 'created_by' => Auth::id(), + 'updated_by' => Auth::id(), + 'details' => $details ?? [], + 'mimetype' => $mimetype, + ]); + } + + private static function insertFileList(int $contentuid, string $title, string $filename, string $description, $categories, $details, $tags = [], $hidden = 0, ?string $file_type = null) + { + if (!FileContent::where('id', $contentuid)->exists()) { + throw new \Exception("File Content does not exist"); + } + return FileList::create([ + 'contentuid' => $contentuid, + 'hashkey' => hash('sha256', uniqid((string) now(), true)), + 'title' => $title, + 'filename' => $filename, + 'description' => $description, + 'categories' => $categories, + 'details' => $details, + 'tags' => $tags, + 'hidden' => $hidden, + 'file_type' => $file_type, + 'is_public' => false, + 'created_by' => Auth::id(), + 'updated_by' => Auth::id(), + ]); + } + + public static function uploadFileList(string|UploadedFile $fileData, string $title, string $filename, ?string $description = null, ?array $details = [], $categories = null, $tags = [], $hidden = 0, ?string $file_type = null) + { + try { + $fileContent = self::uploadFileContent($fileData, $title, $description, $details); + return self::insertFileList($fileContent->id, $title, $filename, $description, $categories, $details, $tags, $hidden, $file_type); + } catch (\Throwable $th) { + return Response::json($th->getMessage(), 500); + } + } + + public static function viewFilebyFileListHash(string $filelist_hash) + { + $filelist = FileList::where('hashkey', $filelist_hash)->first(); + if (!$filelist) abort(404, 'File not found'); + + $cdnUrl = trim((string) ($filelist->cdn_url ?? '')); + if ($cdnUrl !== '') return redirect($cdnUrl); + + $fileContent = $filelist->fileContent; + if (!$fileContent) abort(404, 'File content not found'); + + $content = $fileContent->content; + $driver = DB::connection()->getDriverName(); + if (is_resource($content) && $driver !== 'mysql') $content = stream_get_contents($content); + if ($driver === 'mysql') $content = base64_decode($content); + + $finfo = new \finfo(FILEINFO_MIME_TYPE); + $mimeType = $finfo->buffer($content); + + $mimeMap = [ + 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', + 'image/webp' => 'webp', 'application/pdf' => 'pdf', 'text/plain' => 'txt', + 'application/zip' => 'zip', 'application/json' => 'json', + ]; + $extension = $mimeMap[$mimeType] ?? pathinfo($filelist->filename, PATHINFO_EXTENSION); + $filename = pathinfo($filelist->filename, PATHINFO_FILENAME) . '.' . $extension; + + return Response::make($content, 200, [ + 'Content-Type' => $mimeType, + 'Content-Disposition' => 'inline; filename="' . $filename . '"', + 'Content-Length' => strlen($content), + ]); + } + + public static function generateURLforFileListHash(string $filelist_hashkey): string + { + $cdnUrl = FileList::where('hashkey', $filelist_hashkey)->value('cdn_url'); + if (is_string($cdnUrl) && trim($cdnUrl) !== '') return $cdnUrl; + return "/RequestData/File/$filelist_hashkey"; + } + + public static function UploadFilefromRequest(Request $request, string $category) + { + if (!Auth::check() || !UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::UploadAllFiles)) { + return response()->json(['error' => 'Unauthorized'], 403); + } + + if (!$request->hasFile('file')) { + return response()->json(false, 400); + } + + $file = $request->file('file'); + $filename = $file->getClientFilename(); + $result = self::uploadFileList($file, '', $filename ?? '', '', [], $category, [], 0, null); + + try { + if (is_string($result->hashkey) && !empty($result->hashkey)) { + return response()->json([ + 'success' => true, + 'hashkey' => $result->hashkey, + 'message' => 'File uploaded successfully', + 'url' => $result->resolvedUrl(), + 'name' => $filename, + ], 200); + } + throw new \Exception("File upload failed"); + } catch (\Throwable $th) { + return response()->json(['success' => false, 'error' => 'File upload failed'], 500); + } + } +} diff --git a/app/Http/Controllers/FilesMainController.php b/app/Http/Controllers/FilesMainController.php deleted file mode 100644 index 8a86c69..0000000 --- a/app/Http/Controllers/FilesMainController.php +++ /dev/null @@ -1,412 +0,0 @@ - $threshold; - } - - private static function insertFileContentPostgresPDO(array $data) - { - $pdo = DB::getPdo(); - - $stmt = $pdo->prepare(' - INSERT INTO file_content - (hashkey, filehash, titlename, description, size_in_bytes, content, filelocation, created_by, updated_by, details, created_at, updated_at) - VALUES - (:hashkey, :filehash, :titlename, :description, :size_in_bytes, :content, :filelocation, :created_by, :updated_by, :details, :created_at, :updated_at) - '); - - $now = now()->toDateTimeString(); - - $stmt->bindParam(':hashkey', $data['hashkey']); - $stmt->bindParam(':filehash', $data['filehash']); - $stmt->bindParam(':titlename', $data['titlename']); - $stmt->bindParam(':description', $data['description']); - $stmt->bindParam(':size_in_bytes', $data['size_in_bytes']); - $stmt->bindParam(':content', $data['content'], PDO::PARAM_LOB); - $stmt->bindParam(':filelocation', $data['filelocation']); - $stmt->bindParam(':created_by', $data['created_by']); - $stmt->bindParam(':updated_by', $data['updated_by']); - $detailsJson = json_encode($data['details']); - $stmt->bindParam(':details', $detailsJson); - $stmt->bindParam(':created_at', $now); - $stmt->bindParam(':updated_at', $now); - - $stmt->execute(); - - // Optionally, get the inserted ID and fetch the model - $id = DB::getPdo()->lastInsertId(); - - return FileContent::find($id); - } - - private static function insertFileContentSql(array $data) - { - $now = Carbon::now(); - - $data['content'] = base64_encode($data['content']); - $data['details'] = json_encode($data['details']); - $data['created_at'] = $now; - $data['updated_at'] = $now; - - return FileContent::create($data); - } - - - private static function insertFileContent(array $data) - { - $driver = DB::connection()->getDriverName(); - - if ($driver === 'pgsql') { - return self::insertFileContentPostgresPDO($data); - } - - if ($driver === 'mysql') { - return self::insertFileContentSql($data); - } - - throw new \RuntimeException("Unsupported database driver: {$driver}"); - } - - - - /** - * Upload file content and store metadata in DB. - * - * @param \Hypervel\Http\UploadedFile|string $fileData // filepath, filebinaryData, UploadedFileRequest - * @param string $title - * @param string|null $description - * @param string|null $filelocation - * @param array|null $details - */ - private static function uploadFileContent(UploadedFile|string $fileData, string $title, ?string $description = null, ?array $details = []) - { - - - if ($fileData instanceof UploadedFile) { - $fileHash = hash_file('sha256', $fileData->getRealPath()); - $fileSize = $fileData->getSize(); - $filename = $fileHash; - $fileContent = File::get($fileData->getRealPath()); - $path = $fileData->storeAs('files', $filename); - } elseif (is_string($fileData) && file_exists($fileData)) { - - $fileHash = hash_file('sha256', $fileData); - $fileSize = filesize($fileData); - $fileContent = file_get_contents($fileData); - $path = Storage::put("files/{$fileHash}", $fileContent); - - } elseif (self::isLikelyBinary($fileData)) { - $fileHash = hash('sha256', $fileData); - $fileSize = strlen($fileData); - // $path = Storage::putFile('files', $fileData); - $fileContent = $fileData; - $path = Storage::put("files/{$fileHash}", $fileContent); - } else { - throw new \InvalidArgumentException('Invalid file data provided.'); - } - - $hashKey = hash('sha256', uniqid((string) now(), true)); - - - $finfo = new \finfo(FILEINFO_MIME_TYPE); - $mimetype = $finfo->buffer($fileContent); - - // $fileContent = FileContent::create([ - // 'hashkey' => $hashKey, - // 'filehash' => $fileHash, - // 'titlename' => $title, - // 'description' => $description, - // 'size_in_bytes' => $fileSize, - // 'content' => $fileContent, - // 'filelocation' => $filelocation ?? $path, - // 'created_by' => Auth::id(), - // 'updated_by' => Auth::id(), - // 'details' => $details ?? [], - // ]); - - $fileContentDB = FileContent::where('filehash', $fileHash)->first(); - - if ($fileContentDB) { - return $fileContentDB; - } - - - - - - $fileContentDB = self::insertFileContent([ - 'hashkey' => $hashKey, - 'filehash' => $fileHash, - 'titlename' => $title, - 'description' => $description, - 'size_in_bytes' => $fileSize, - 'content' => $fileContent, - 'filelocation' => $filelocation ?? $path, - 'created_by' => Auth::id(), - 'updated_by' => Auth::id(), - 'details' => $details ?? [], - 'mimetype' => $mimetype - ]); - - return $fileContentDB; - } - - - private static function insertFileList(int $contentuid, string $title, string $filename, string $description, $categories, $details, $tags = [], $hidden = 0, ?string $file_type = null) - { - $filecontent_exists = FileContent::where('id', $contentuid)->exists(); - if (!$filecontent_exists) { - throw new \Exception("File Content Does not Exist", 1); - } - - $data = [ - 'contentuid' => $contentuid, - 'hashkey' => hash('sha256', uniqid((string) now(), true)), - 'title' => $title, - 'filename' => $filename, - 'description' => $description, - 'categories' => $categories, - 'details' => $details, - 'tags' => $tags, - 'hidden' => $hidden, - 'file_type' => $file_type, - 'is_public' => false, - 'created_by' => Auth::id(), - 'updated_by' => Auth::id(), - ]; - $filelist = FileList::create($data); - - - - return $filelist; - - } - - public static function uploadFileList(string|UploadedFile $fileData, string $title, string $filename, ?string $description = null, ?array $details = [], $categories = null, $tags = [], $hidden = 0, ?string $file_type = null) - { - try { - $fileContent = self::uploadFileContent($fileData, $title, $description, $details); - $filecontent_id = $fileContent->id; - // print_r($fileContent); - return self::insertFileList($filecontent_id, $title, $filename, $description, $categories, $details, $tags, $hidden, $file_type); - } catch (\Throwable $th) { - return Response::json($th->getMessage(), 500); - // return Response::make('Error uploading file content: ' . $th->getMessage(), 500); - return false; - } - - - } - - public static function viewFilebyFileListHash(string $filelist_hash) - { - $filelist = FileList::where('hashkey', $filelist_hash)->first(); - - // return Response::json($filelist, 500); - - if (!$filelist) { - abort(404, 'File not found'); - } - - $cdnUrl = trim((string) ($filelist->cdn_url ?? '')); - if ($cdnUrl !== '') { - return redirect($cdnUrl); - } - - $fileContent = $filelist->fileContent; - - if (!$fileContent) { - abort(404, 'File content not found'); - } - - $content = $fileContent->content; - - $driver = DB::connection()->getDriverName(); - - if (is_resource($content) && $driver !== 'mysql') { - $content = stream_get_contents($content); - } - - if ($driver === 'mysql') { - $content = base64_decode($content); - } - - - $finfo = new \finfo(FILEINFO_MIME_TYPE); - $mimeType = $finfo->buffer($content); - - // Map common MIME types to extensions - $mimeMap = [ - 'image/jpeg' => 'jpg', - 'image/png' => 'png', - 'image/gif' => 'gif', - 'image/webp' => 'webp', - 'application/pdf' => 'pdf', - 'text/plain' => 'txt', - 'text/html' => 'html', - 'application/zip' => 'zip', - 'application/json' => 'json', - 'audio/mpeg' => 'mp3', - 'video/mp4' => 'mp4', - // add more as needed - ]; - - // Pick extension from map, or fallback - $extension = $mimeMap[$mimeType] ?? pathinfo($filelist->filename, PATHINFO_EXTENSION); - - // Build safe filename with extension - $filename = pathinfo($filelist->filename, PATHINFO_FILENAME) . '.' . $extension; - - return Response::make($content, 200, [ - 'Content-Type' => $mimeType, - 'Content-Disposition' => 'inline; filename="' . $filename . '"', - 'Content-Length' => strlen($content), - ]); - - // return Response::make($content, 200, [ - // 'Content-Type' => 'application/octet-stream', - // 'Content-Disposition' => 'inline; filename="' . $filelist->filename . '"', - // 'Content-Length' => strlen($content), - // ]); - } - - private static function canUploadCategory(string $category): bool - { - if (!Auth::check()) { - return false; - } - - if (UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::UploadAllFiles)) { - return true; - } - - if (strcasecmp($category, 'ProductMarket') === 0) { - return ProductPermissions::isActionAllowed(UserActions::CreateProductForOwnStore) - || ProductPermissions::isActionAllowed(UserActions::AddProducttoOwnStore); - } - - return false; - } - - public static function UploadFilefromRequest(Request $request, string $category) - { - if (!self::canUploadCategory($category)) { - return response()->json(['error' => 'Unauthorized'], 403); - } - - if (!$request->hasFile('file')) { - // $responseData =['success' => false, 'message' => 'No file uploaded']; - - $responseData = false; - return response()->json(false, 400); - } - - - $file = $request->file('file'); - $filename = $file->getClientFilename(); - $result = self::uploadFileList( - $file, - '', - $filename ?? '', - '', - [], - $category, - [], - 0, - null, - ); - - // return response()->json(['success' => false, 'error' => 'File upload failed'], 500); - - // return $result; - $file_url = $result->resolvedUrl(); - - try { - - if (is_string($result->hashkey) && !empty($result->hashkey)) { - return response()->json(['success' => true, 'hashkey' => $result->hashkey, 'message' => 'File uploaded successfully', 'url' => $file_url, 'name' => $filename], 200); - } else { - throw new \Exception("File upload failed", 1); - } - } catch (\Throwable $th) { - return response()->json(['success' => false, 'error' => 'File upload failed'], 500); - } - - } - - - - public static function generateURLforFileListHash(string $filelist_hashkey) - { - $cdnUrl = FileList::where('hashkey', $filelist_hashkey)->value('cdn_url'); - if (is_string($cdnUrl) && trim($cdnUrl) !== '') { - return $cdnUrl; - } - return "/RequestData/File/$filelist_hashkey"; - // return route('requestdata.file.view', ['hash' => $filelist_hashkey]); - } - - public static function bladePreloadFileScript(string|FileList $FileHash): null|string - { - try { - if ($FileHash instanceof FileList) { - - $filecontent = $FileHash->fileContent->content; - - $mimeType = $FileHash->fileContent->mimetype ?? 'application/octet-stream'; - $FileHash = $FileHash->hashkey; - } else { - $FileHash = FileList::where('hashkey', $FileHash)->firstOrFail(); - $filecontent = $FileHash->fileContent->content; - $mimeType = $FileHash->fileContent->mimetype; - $FileHash = $FileHash->hashkey; - } - } catch (\Throwable $th) { - return ''; - } - - - $base64 = base64_encode($filecontent); - - $htmlString = "reqcacheupdateBase64toFile('$FileHash','$base64','$mimeType')"; - return $htmlString; - } - -} diff --git a/app/Http/Controllers/GlobalTransactionController.php b/app/Http/Controllers/GlobalTransactionController.php deleted file mode 100644 index bce1a7d..0000000 --- a/app/Http/Controllers/GlobalTransactionController.php +++ /dev/null @@ -1,177 +0,0 @@ -acct_type, UserActions::ViewGlobalTransactions)) { - return ResponseHelper::returnUnauthorized(); - } - - $user = Auth::user(); - - $productIdHash = $request->input('product_id'); - $storeIdHash = $request->input('store_id'); - - $query = GlobalTransaction::with(['user:id,name,hashkey', 'product:id,name,hashkey', 'store:id,name,hashkey']); - - if ($productIdHash) { - $product = Product::where('hashkey', $productIdHash)->first(); - if ($product) { - $query->where('product_id', $product->id); - } else { - return response()->json([]); - } - } - - if ($storeIdHash) { - $store = Store::where('hashkey', $storeIdHash)->first(); - if ($store) { - $query->where('store_id', $store->id); - } - } - - // Access Control: Ultimate can see everything. - // Others see transactions they created or relate to them. - if ($user->acct_type !== UserTypes::ULTIMATE) { - $query->where(function($q) use ($user) { - $q->where('user_id', $user->id) - ->orWhere('created_by', $user->id); - }); - } - - $transactions = $query->orderBy('created_at', 'desc')->get(); - - // Transform for frontend - $transformed = $transactions->map(function ($tx) { - return [ - 'id' => $tx->id, - 'hashkey' => $tx->hashkey, - 'amount' => $tx->amount, - 'type' => $tx->type ? $tx->type->label() : 'Unknown', - 'status' => $tx->status, - 'description' => $tx->description, - 'created_at' => $tx->created_at, - 'flow' => $tx->flow ? [ - 'value' => $tx->flow->value, - 'label' => match($tx->flow) { - TransactionFlow::INCOME => 'Income', - TransactionFlow::EXPENSE => 'Expense', - TransactionFlow::NEUTRAL => 'Neutral', - default => 'Unknown' - } - ] : null, - 'user' => $tx->user ? $tx->user->name : null, - 'product' => $tx->product ? $tx->product->name : null, - 'store' => $tx->store ? $tx->store->name : null, - ]; - }); - - return response()->json($transformed); - } - - /** - * Create a new global transaction. - * Handles POST /admin/transactions/create - */ - public function store(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::CreateGlobalTransaction)) { - return ResponseHelper::returnUnauthorized(); - } - - $user = Auth::user(); - - $validator = Validator::make($request->all(), [ - 'user_hash' => 'nullable|string', - 'amount' => 'required|numeric', - 'type' => 'required|integer', - 'description' => 'nullable|string', - 'product_hash' => 'nullable|string', - 'store_hash' => 'nullable|string', - 'status' => 'nullable|string', - ]); - - if ($validator->fails()) { - return response()->json(['success' => false, 'errors' => $validator->errors()], 422); - } - - $data = $validator->validated(); - - // Resolve IDs from hashes - $targetUserId = $user->id; - if (isset($data['user_hash'])) { - $targetUser = UserController::findUserIdByHash($data['user_hash']); - if ($targetUser) $targetUserId = $targetUser; - } - - $productId = null; - if (isset($data['product_hash'])) { - $product = Product::where('hashkey', $data['product_hash'])->first(); - if ($product) $productId = $product->id; - } - - $storeId = null; - if (isset($data['store_hash'])) { - $store = Store::where('hashkey', $data['store_hash'])->first(); - if ($store) $storeId = $store->id; - } - - /** @var GlobalTransaction $transaction */ - $transaction = GlobalTransaction::create([ - 'user_id' => $targetUserId, - 'amount' => $data['amount'], - 'type' => $data['type'], - 'description' => $data['description'] ?? '', - 'product_id' => $productId, - 'store_id' => $storeId, - 'status' => $data['status'] ?? 'completed', - 'flow' => $request->has('flow') ? $request->input('flow') : ProductTransactionType::from($data['type'])->flow(), - 'created_by' => $user->id, - ]); - - return response()->json([ - 'success' => true, - 'message' => 'Transaction recorded successfully', - 'hashkey' => $transaction->hashkey - ]); - } - - /** - * Get all product transaction types for dropdown selection. - */ - public function getTypes() - { - $types = collect(ProductTransactionType::cases())->map(function ($type) { - return [ - 'value' => $type->value, - 'label' => $type->label(), - 'flow' => $type->flow()->value, - 'flow_label' => $type->flow()->name - ]; - }); - - return response()->json($types); - } -} diff --git a/app/Http/Controllers/Helpers/Legacy/LibLegacy.php b/app/Http/Controllers/Helpers/Legacy/LibLegacy.php index 77aa07e..ad139d7 100644 --- a/app/Http/Controllers/Helpers/Legacy/LibLegacy.php +++ b/app/Http/Controllers/Helpers/Legacy/LibLegacy.php @@ -11,8 +11,6 @@ use App\Models\User; use Hypervel\Support\Facades\Auth; use App\Enums\UserActions; use App\Http\Controllers\Helpers\QueryHelper; -use App\Models\Market\Product; -use App\Models\Market\Store; use Exception; class LibLegacy diff --git a/app/Http/Controllers/Helpers/Permissions/ProductPermissions.php b/app/Http/Controllers/Helpers/Permissions/ProductPermissions.php deleted file mode 100644 index fc4b485..0000000 --- a/app/Http/Controllers/Helpers/Permissions/ProductPermissions.php +++ /dev/null @@ -1,191 +0,0 @@ -acct_type; - } catch (Exception $e) { - return false; - } - - $defaultRoles = ProductPermissionsDefinition::getAllowedUserTypesAction($acct_type); - $additionalRoles = UserPermissions::isUserAllowedbyAdditionalRoles($userAction); - $deniedRoles = UserPermissions::isUserDeniedRoles($userAction); - - if ($deniedRoles) { - return false; - } - - if (!in_array($userAction, $defaultRoles, true) && !$additionalRoles) { - return false; - } - - if (!ProductPermissionsDefinition::doesActionRequireDirectChildren($userAction)) { - return true; - } - - if (!$storeHashorID && !$productHashorID) { - return false; - } - - $store = null; - $product = null; - - if ($storeHashorID) { - $store = QueryHelper::findOrNullByHashOrId($storeHashorID, Store::class); - } - - if ($productHashorID) { - $product = QueryHelper::findOrNullByHashOrId($productHashorID, Product::class); - } - - if (!$store && !$product) { - return false; - } - - // Determine store from product if needed - if (!$store && $product) { - $store = $product->store ?? null; - } - - if (!$store) { - return false; - } - - $storeOwner = $store->owner; - if ($storeOwner && UserPermissions::isDescendantOfCurrentUser($storeOwner)) { - return true; - } - - // Check all managers in the new store_managers table - $managerIds = $store->managerUsers()->pluck('users.id')->toArray(); - foreach ($managerIds as $managerId) { - if (UserPermissions::isDescendantOfCurrentUser($managerId)) { - return true; - } - } - - // Legacy manager check - if ($store->manager_id && UserPermissions::isDescendantOfCurrentUser($store->manager_id)) { - return true; - } - - return false; -} - - - -} - - -class ProductPermissionsDefinition -{ - - public static function getAllowedUserTypesAction(UserTypes $currentUserType) - { - return match ($currentUserType) { - UserTypes::ULTIMATE => UserActions::cases(), - - UserTypes::SUPER_OPERATOR => [ - UserActions::CreateStoreforSelf, - UserActions::CreateStoreGlobal, - UserActions::ModifyAllStores, - UserActions::ModifyOwnStore, - UserActions::CreateProductGlobal, - UserActions::CreateProductForOwnStore, - UserActions::CreateProductforSelf, - UserActions::ModifyAllProducts, - UserActions::ModifyOwnProduct, - UserActions::AddProducttoOwnStore, - UserActions::AddProducttoAnyStore, - UserActions::RemoveProductfromAnyStore, - ], - - UserTypes::OPERATOR => [ - UserActions::CreateStoreforSelf, - UserActions::CreateStoreGlobal, - UserActions::ModifyAllStores, - UserActions::ModifyOwnStore, - UserActions::CreateProductGlobal, - UserActions::CreateProductForOwnStore, - UserActions::CreateProductforSelf, - UserActions::ModifyAllProducts, - UserActions::ModifyOwnProduct, - UserActions::AddProducttoOwnStore, - UserActions::AddProducttoAnyStore, - UserActions::RemoveProductfromAnyStore, - ], - - UserTypes::STORE_OWNER => [ - UserActions::ModifyOwnStore, - UserActions::ModifyOwnProduct, - UserActions::AddProducttoOwnStore, - UserActions::CreateProductForOwnStore, - ], - - UserTypes::STORE_MANAGER => [ - UserActions::ModifyOwnProduct, - UserActions::AddProducttoOwnStore, - UserActions::CreateProductForOwnStore, - ], - - default => [], - }; - } - - public static function doesActionRequireDirectChildren(UserActions $userAction) - { - return match ($userAction) { - UserActions::CreateStoreforSelf => true, - UserActions::CreateStoreGlobal => false, - UserActions::ModifyAllStores => false, - UserActions::ModifyOwnStore => true, - UserActions::CreateProductGlobal => false, - UserActions::CreateProductforSelf => true, - UserActions::ModifyAllProducts => false, - UserActions::ModifyOwnProduct => true, - UserActions::AddProducttoOwnStore=>true, - UserActions::AddProducttoAnyStore=>false, - UserActions::RemoveProductfromAnyStore=>false, - default => false, - }; - } - - - - - -} \ No newline at end of file diff --git a/app/Http/Controllers/Helpers/Permissions/UserPermissions.php b/app/Http/Controllers/Helpers/Permissions/UserPermissions.php index 0f0828e..41e6528 100644 --- a/app/Http/Controllers/Helpers/Permissions/UserPermissions.php +++ b/app/Http/Controllers/Helpers/Permissions/UserPermissions.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace App\Http\Controllers\Helpers\Permissions; use App\Enums\UserTypes; -use Hypervel\Http\Request; use App\Models\User; use Hypervel\Support\Facades\Auth; use App\Enums\UserActions; @@ -15,151 +14,92 @@ class UserPermissions { use Roles; use PermissionsCheck; + public static function IsParentofTargetUser(string|int $hashkeyORId) { $currentUser = Auth::user(); - if (!$currentUser) { - return false; - } - if (is_numeric($hashkeyORId)) { - $targetUser = User::find($hashkeyORId); - } else { - $targetUser = User::where('hashkey', $hashkeyORId)->first(); - } + if (!$currentUser) return false; - if (!$targetUser) { - return false; - } + $targetUser = is_numeric($hashkeyORId) + ? User::find($hashkeyORId) + : User::where('hashkey', $hashkeyORId)->first(); + + if (!$targetUser) return false; return $targetUser->parentuid === $currentUser->id; } - /** - * Check if the current user is an ancestor or part of the family of the target user. - * The family includes both ancestors (parents, grandparents, etc.) and descendants. - * - * @param string|int $hashkeyORId - Target user's hashkey or ID - * @return bool - */ public static function isAncestorOrFamilyOfTargetUser(string|int $hashkeyORId): bool { $currentUser = Auth::user(); - if (!$currentUser) { - return false; - } + if (!$currentUser) return false; - if (is_numeric($hashkeyORId)) { - $targetUser = User::find($hashkeyORId); - } else { - $targetUser = User::where('hashkey', $hashkeyORId)->first(); - } + $targetUser = is_numeric($hashkeyORId) + ? User::find($hashkeyORId) + : User::where('hashkey', $hashkeyORId)->first(); + + if (!$targetUser) return false; + if ($currentUser->id === $targetUser->id) return true; + if ($targetUser->getAllDescendants()->contains($currentUser)) return true; - if (!$targetUser) { - return false; - } - if ($currentUser->id === $targetUser->id) { - return true; - } - if ($targetUser->getAllDescendants()->contains($currentUser)) { - return true; - } $parent = $targetUser->parent; while ($parent) { - if ($parent->id === $currentUser->id) { - return true; - } + if ($parent->id === $currentUser->id) return true; $parent = $parent->parent; } - return false; } - /** - * Check if the current user is an indirect parent of the target user. - * An indirect parent is someone who is an ancestor, but not a direct parent. - * - * @param string|int $hashkeyORId - Target user's hashkey or ID - * @return bool - */ public static function isIndirectParentOfTargetUser(string|int $hashkeyORId): bool { $currentUser = Auth::user(); - if (!$currentUser) { - return false; - } + if (!$currentUser) return false; - if (is_numeric($hashkeyORId)) { - $targetUser = User::find($hashkeyORId); - } else { - $targetUser = User::where('hashkey', $hashkeyORId)->first(); - } + $targetUser = is_numeric($hashkeyORId) + ? User::find($hashkeyORId) + : User::where('hashkey', $hashkeyORId)->first(); + + if (!$targetUser) return false; + if ($currentUser->id === $targetUser->id) return false; - if (!$targetUser) { - return false; - } - if ($currentUser->id === $targetUser->id) { - return false; - } $descendants = $targetUser->getAllDescendants(); if ($descendants->contains($currentUser)) { - if ($targetUser->parentuid === $currentUser->id) { - return false; - } + if ($targetUser->parentuid === $currentUser->id) return false; return true; } return false; } + private static function safeUserActionFromString(string $value): ?UserActions { foreach (UserActions::cases() as $case) { - if ($case->value === $value) { - return $case; - } + if ($case->value === $value) return $case; } - return null; } public static function isDescendantOfCurrentUser(string|int|User|null $hashkeyOrId): bool { - if (!$hashkeyOrId) { - return false; - } + if (!$hashkeyOrId) return false; $currentUser = Auth::user(); - if (!$currentUser) { - return false; - } + if (!$currentUser) return false; - - - if ($hashkeyOrId instanceof User) { - $targetUser = $hashkeyOrId; - } else { - $targetUser = is_numeric($hashkeyOrId) + $targetUser = $hashkeyOrId instanceof User + ? $hashkeyOrId + : (is_numeric($hashkeyOrId) ? User::find($hashkeyOrId) - : User::where('hashkey', $hashkeyOrId)->first(); - } - - - - if (!$targetUser || $currentUser->id === $targetUser->id) { - return false; - } + : User::where('hashkey', $hashkeyOrId)->first()); + if (!$targetUser || $currentUser->id === $targetUser->id) return false; $descendants = $currentUser->getAllDescendants(); - return $descendants->contains('id', $targetUser->id); } - public static function isActionPermitted(string|int|UserTypes $hashkeyORId, UserActions $userActions) { - $currentUser = Auth::user(); - if (!Auth::check()) { - return false; - } + if (!Auth::check()) return false; $currentUserType = $currentUser->acct_type; if (!($currentUserType instanceof UserTypes)) { @@ -167,12 +107,8 @@ class UserPermissions } $isDeniedRoles = self::isUserDeniedRoles($userActions); + if ($isDeniedRoles) return false; - if ($isDeniedRoles) { - return false; - } - - //Started Changing this part, For actions that does not target a user if (self::CheckifRoleDoesNotRequireaTargetUser($userActions)) { $preliminary_permission = true; } elseif (($hashkeyORId || $hashkeyORId === 0 || $hashkeyORId === '0') && !($hashkeyORId instanceof UserTypes)) { @@ -182,55 +118,25 @@ class UserPermissions } else { $preliminary_permission = false; } - //end - if (!$preliminary_permission) { - return false; - } + if (!$preliminary_permission) return false; + $permissionString = $userActions->value; - - // if (isset(self::$roles[$currentUserType]) && in_array($permissionString, self::$roles[$currentUserType])) { - // return true; - // } else { - // return false; - // } - $permissionEnum = self::safeUserActionFromString($permissionString); - $allowedThroughAdditionalRoles = self::isUserAllowedbyAdditionalRoles($userActions); - // if (isset(self::$roles[$currentUserType])) { - // file_put_contents('php://stderr', print_r($currentUserType, true) . "\n"); - // } $isPermissionAllowed = (isset(self::roles()[$currentUserType->value]) && in_array($permissionEnum, self::roles()[$currentUserType->value])); - if ( - $permissionEnum && ($isPermissionAllowed || - $allowedThroughAdditionalRoles) - ) { - return true; - } else { - return false; - } + return $permissionEnum && ($isPermissionAllowed || $allowedThroughAdditionalRoles); } - - /** - * Check if CurrentUser is Allowed to Modify Target User based on User type - * @param string|int $hashkeyORId - * @return bool - */ private static function isUserPreliminaryPermissionAllowed(string|int|UserTypes $hashkeyORId) { $currentUser = Auth::user(); - if (!$currentUser) { - return false; - } + if (!$currentUser) return false; - if ($currentUser->acct_type === UserTypes::ULTIMATE) { - return true; - } + if ($currentUser->acct_type === UserTypes::SUPER_ADMIN) return true; $currentUserType = $currentUser->acct_type; if (!($currentUserType instanceof UserTypes)) { @@ -238,118 +144,55 @@ class UserPermissions } $allowedUserTypes = UserTypeService::getAllowedUserTypes($currentUserType); - - - - //Updated This Part if ($hashkeyORId instanceof UserTypes) { $isTypeAllowedtobeModified = in_array($hashkeyORId, $allowedUserTypes); } else { - try { - if (is_string($hashkeyORId)) { - $TargetUser = User::where('hashkey', $hashkeyORId)->first(); - $target_acct_type = $TargetUser->acct_type; - } else { - $TargetUser = User::where('id', $hashkeyORId)->first(); - $target_acct_type = $TargetUser->acct_type; - } + $TargetUser = is_string($hashkeyORId) + ? User::where('hashkey', $hashkeyORId)->first() + : User::where('id', $hashkeyORId)->first(); + $target_acct_type = $TargetUser->acct_type; $isTypeAllowedtobeModified = in_array($target_acct_type, $allowedUserTypes); - } catch (\Throwable $th) { throw new \Exception('' . $th->getMessage()); } - } - //end - - $IndirectParent = self::isDescendantOfCurrentUser($hashkeyORId); $isSelf = $currentUser->hashkey === $hashkeyORId; return ($IndirectParent || $isSelf) && $isTypeAllowedtobeModified; - - } - - /** - * Private function to check if Addional Roles is allowed way beyond the Roles of the User Type - * @param \App\Enums\UserActions $userActions - * @return bool - */ - // private static function isUserAllowedbyAdditionalRoles(UserActions $userActions): bool - // { - // $currentUser = User::findOrFail(Auth::id()); - // if (!$currentUser) { - // return false; - // } - - // $currentUserDeniedRoles = $currentUser->additional_roles ?? []; - // if (empty($currentUserDeniedRoles)) { - // return false; - // } - // if (in_array($userActions->value, $currentUserDeniedRoles)) { - // return true; - // } else { - // return false; - // } - // // - // } - public static function isUserAllowedbyAdditionalRoles(UserActions $userActions): bool { $currentUser = Auth::user(); - if (!$currentUser) - return false; + if (!$currentUser) return false; $additionalRoles = $currentUser->additional_roles ?? []; - if (empty($additionalRoles)) { - return false; - } + if (empty($additionalRoles)) return false; foreach ($additionalRoles as $role) { if ($role instanceof UserActions) { - if ($role === $userActions) { - return true; - } + if ($role === $userActions) return true; } elseif (is_string($role)) { - if ($role === $userActions->value) { - return true; - } + if ($role === $userActions->value) return true; } } - return false; } - /** - * Private function to check if A role is denied in the custom denied roles in user table - * TAKES PRECEDENCE OVER ANYTHING - * @param \App\Enums\UserActions $userActions - * @return bool - */ public static function isUserDeniedRoles(UserActions $userActions) { $currentUser = User::findOrFail(Auth::id()); - if (!$currentUser) { - return false; - } + if (!$currentUser) return false; - if ($currentUser->acct_type === UserTypes::ULTIMATE) { - return false; - } + if ($currentUser->acct_type === UserTypes::SUPER_ADMIN) return false; $currentUserAdditionalRoles = $currentUser->denied_roles ?? []; - if (empty($currentUserAdditionalRoles)) { - return false; - } - if (in_array($userActions->value, $currentUserAdditionalRoles)) { - return true; - } else { - return false; - } + if (empty($currentUserAdditionalRoles)) return false; + + return in_array($userActions->value, $currentUserAdditionalRoles); } public static function getUserRoles(int $id) @@ -361,11 +204,7 @@ class UserPermissions } $acct_type = $currentUser->acct_type; - $defaultuserRoles = self::roles()[$acct_type->value] ?? []; - // if (!$defaultuserRoles) { - // return false; - // } $additionalRoles = $currentUser->additional_roles ?? []; $deniedRoles = $currentUser->denied_roles ?? []; $mergedRoles = array_merge($defaultuserRoles, $additionalRoles); @@ -373,46 +212,24 @@ class UserPermissions foreach ($mergedRoles as $role) { $uniqueRoles[$role->value] = $role; } - foreach ($deniedRoles as $denied) { unset($uniqueRoles[$denied->value]); } - return array_values($uniqueRoles); - } - public static function normalizeRole(UserActions|string $role): UserActions { - if ($role instanceof UserActions) { - return $role; - } - - // Try as backing value, e.g. "create_user" - if ($e = UserActions::tryFrom($role)) { - return $e; - } - - // Try as CASE NAME, e.g. "CreateUser" + if ($role instanceof UserActions) return $role; + if ($e = UserActions::tryFrom($role)) return $e; foreach (UserActions::cases() as $case) { - if ($case->name === $role) { - return $case; - } + if ($case->name === $role) return $case; } - - // Try to convert PascalCase to snake_case and match: "CreateUser" -> "create_user" $snake = strtolower(preg_replace('/(?first()); if (!$ancestorUser || !$descendantUser) return false; - if ($ancestorUser->id === $descendantUser->id) return true; $parent = $descendantUser->parent; while ($parent) { - if ($parent->id === $ancestorUser->id) { - return true; - } + if ($parent->id === $ancestorUser->id) return true; $parent = $parent->parent; } - return false; } - - /** - * Check if a user is allowed to access a specific store. - */ - public static function isUserAllowedAccessToStore(User|int|string $user, \App\Models\Market\Store|int|string $store): bool - { - $userObj = $user instanceof User ? $user : (is_numeric($user) ? User::find((int)$user) : User::where('hashkey', $user)->first()); - if (!$userObj) return false; - - if ($userObj->acct_type === UserTypes::ULTIMATE) return true; - - $storeObj = $store instanceof \App\Models\Market\Store ? $store : (is_numeric($store) ? \App\Models\Market\Store::find((int)$store) : \App\Models\Market\Store::where('hashkey', $store)->first()); - if (!$storeObj) return false; - - // Check if user owns or manages the store - if ($userObj->id === $storeObj->owner_id || $userObj->id === $storeObj->manager_id) return true; - - // Check if user's parent is the owner/manager (for POS_TERMINAL/RIDER) - if ($userObj->parentuid === $storeObj->owner_id || $userObj->parentuid === $storeObj->manager_id) return true; - - // check if user is an ancestor of the owner/manager - if (self::isAncestorOf($userObj, $storeObj->owner_id) || self::isAncestorOf($userObj, $storeObj->manager_id)) return true; - - return false; - } - - } + trait PermissionsCheck { - - /** - * Check if the user modification is allowed based on the provided hashkey or ID. - * - * @param string|int $hashkeyORId - * @return bool - */ public static function isUserModificationAllowed(string|int $hashkeyORId): bool { return self::isActionPermitted($hashkeyORId, UserActions::ModifyUser); } - /** - * Check if the user can be set to active based on the provided hashkey or ID. - * - * @param string|int $hashkeyORId - * @return bool - */ public static function isUserSetActiveAllowed(string|int $hashkeyORId): bool { return self::isActionPermitted($hashkeyORId, UserActions::SetActiveUser); } - /** - * Check if the user can be set to inactive based on the provided hashkey or ID. - * - * @param string|int $hashkeyORId - * @return bool - */ public static function isUserSetInactiveAllowed(string|int $hashkeyORId): bool { return self::isActionPermitted($hashkeyORId, UserActions::SetInActiveUser); } - /** - * Check if the user can be deleted based on the provided hashkey or ID. - * - * @param string|int $hashkeyORId - * @return bool - */ public static function isUserDeletionAllowed(string|int $hashkeyORId): bool { return self::isActionPermitted($hashkeyORId, UserActions::DeleteUser); @@ -525,27 +287,26 @@ trait PermissionsCheck return self::isActionPermitted($hashkeyORId, UserActions::DeleteUserExec); } - public static function isUserNotesViewingAllowed(string|int $hashkeyORId) { return self::isActionPermitted($hashkeyORId, UserActions::ViewUserNotes); } + public static function isUserNotesUpdateAllowed(string|int $hashkeyORId) { return self::isActionPermitted($hashkeyORId, UserActions::SetUserNotes); } + public static function isUserNotesDeletionAllowed(string|int $hashkeyORId) { return self::isActionPermitted($hashkeyORId, UserActions::DeleteUserNotes); } + public static function isUserPasswordChangeAllowed(string|int $hashkeyORId) { return self::isActionPermitted($hashkeyORId, UserActions::ChangeUserPassword); } - public static function isDirectCreditTransfertoUserAllowed(string|int $hashkeyORId) - { - return self::isActionPermitted($hashkeyORId, UserActions::DirectUserCreditTransfer); - } + public static function isForceLogoutUserAllowed(string|int $hashkeyORId) { return self::isActionPermitted($hashkeyORId, UserActions::ForceLogoutUser); @@ -570,13 +331,11 @@ trait PermissionsCheck { return self::isActionPermitted($hashkeyORId, UserActions::ChangeAnotherUsersParent); } - } trait Roles { - public static $RoleswithNoTargetUser = [ UserActions::ViewAllUserTypes, UserActions::ListAllUsersAsParentforUserCreation, @@ -586,51 +345,47 @@ trait Roles UserActions::UploadAllFiles, UserActions::DeleteAllFiles, UserActions::ModifyAllFiles, - UserActions::DeleteAllStores, - UserActions::DeleteAllProducts, - UserActions::ViewAllFiles, UserActions::ViewGlobalReports, - UserActions::AddProducttoAnyStore, UserActions::CreateAnnouncement, UserActions::ModifyAnnouncement, UserActions::DeleteAnnouncement, UserActions::ViewAllAnnouncements, - UserActions::ViewFarmers, - UserActions::VerifyFarmer, - UserActions::CreateOrganization, - UserActions::ViewOrganizations, - UserActions::ViewShipments, - UserActions::CreateShipment, - UserActions::UpdateShipmentStatus, - UserActions::CreateCourier, - UserActions::ViewCouriers, - UserActions::ViewGlobalTransactions, - UserActions::CreateGlobalTransaction, UserActions::ViewAccountingReports, UserActions::ManageAccounting, - UserActions::ViewProperties, - UserActions::ViewReferrals, - UserActions::ViewPosAccessKeys, - UserActions::CreatePosAccessKey, - UserActions::DeletePosAccessKey, - UserActions::TogglePosAccessKey, - UserActions::ViewPosReports, - UserActions::ViewCustomers, UserActions::UltimateConsole, UserActions::UltimateLogs, UserActions::UltimateReports, UserActions::UltimateMaintenance, UserActions::UltimateQuery, - UserActions::UltimateBatch, UserActions::UltimateGlobalMessage, UserActions::UltimateFlush, UserActions::ManageLandingPages, - UserActions::JoinCooperative, UserActions::ManageQrphPaymentCode, UserActions::ViewChapterOrgChart, UserActions::ManageChapterMembers, UserActions::ViewScopedMemberReports, UserActions::AssignChapterOfficer, + UserActions::ViewResidents, + UserActions::ManageResidents, + UserActions::ImportResidents, + UserActions::ExportResidents, + UserActions::ViewHouseholds, + UserActions::ManageHouseholds, + UserActions::ViewDocumentRequests, + UserActions::CreateDocumentRequest, + UserActions::ProcessDocumentRequest, + UserActions::ManageRequestTypes, + UserActions::ManageFeeSchedules, + UserActions::ViewBlotters, + UserActions::CreateBlotter, + UserActions::ProcessBlotter, + UserActions::ManageBlotterHearings, + UserActions::ViewBarangayProjects, + UserActions::ManageBarangayProjects, + UserActions::ViewBarangayBudget, + UserActions::ManageBarangayBudget, + UserActions::ViewFeePayments, + UserActions::ManageFeePayments, ]; public static function CheckifRoleDoesNotRequireaTargetUser(UserActions $userAction): bool @@ -638,395 +393,224 @@ trait Roles return in_array($userAction, self::$RoleswithNoTargetUser, true); } - // public static array $roles = [ - - // UserTypes::ULTIMATE->value => UserActions::cases(), - // // [ - // // UserActions::CreateUser, - // // UserActions::ChangeUserPassword, - // // UserActions::ModifyUser, - // // UserActions::DeleteUser, - // // UserActions::SetActiveUser, - // // UserActions::SetInActiveUser, - // // UserActions::DeveloperConsole, - // // UserActions::UltimateConsole, - // // UserActions::UltimateReports, - // // UserActions::UltimateLogs, - // // UserActions::UpdateSelfExec, - // // UserActions::UpdateUserExec, - // // UserActions::ViewUserExec, - // // UserActions::ViewSelfExec, - // // UserActions::SetUserNotes, - // // UserActions::DeleteUserNotes, - // // UserActions::ViewUserNotes, - // // UserActions::SetSelfNotes, - // // UserActions::DeleteSelfNotes, - // // UserActions::ViewGlobalReports, - // // UserActions::ModifyGlobalReports, - // // UserActions::DeleteGlobalReports, - // // UserActions::DeleteUserExec, - // // UserActions::DirectUserCreditTransfer, - // // ], - - // UserTypes::SUPER_OPERATOR->value => [ - // UserActions::CreateUser, - // UserActions::ModifyUser, - // UserActions::SetActiveUser, - // UserActions::SetInActiveUser, - // UserActions::DeveloperConsole, - // UserActions::ViewGlobalReports, - // ], - - // UserTypes::OPERATOR->value => [ - // UserActions::CreateUser, - // UserActions::ModifyUser, - // UserActions::SetActiveUser, - // UserActions::SetInActiveUser, - // ], - - // ]; - public static function roles() { + // Barangay staff actions shared by most staff roles + $staffReadActions = [ + UserActions::ViewResidents, + UserActions::ViewHouseholds, + UserActions::ViewBlotters, + UserActions::ViewDocumentRequests, + UserActions::ViewBarangayProjects, + UserActions::ViewBarangayBudget, + UserActions::ViewAllAnnouncements, + UserActions::ViewAllFiles, + UserActions::ViewUserInfo, + UserActions::ViewChapterOrgChart, + UserActions::ViewFeePayments, + UserActions::ViewAllUserTypes, + UserActions::ListAllUsersAsParentforUserCreation, + UserActions::CheckifMobileNumberExists, + UserActions::CheckifUsernameExists, + ]; + + $staffManageActions = array_merge($staffReadActions, [ + UserActions::ManageResidents, + UserActions::ImportResidents, + UserActions::ExportResidents, + UserActions::ManageHouseholds, + UserActions::CreateBlotter, + UserActions::ProcessBlotter, + UserActions::ManageBlotterHearings, + UserActions::ProcessDocumentRequest, + UserActions::ManageRequestTypes, + UserActions::ManageFeeSchedules, + UserActions::ManageBarangayProjects, + UserActions::ManageBarangayBudget, + UserActions::ManageFeePayments, + UserActions::CreateAnnouncement, + UserActions::ModifyAnnouncement, + UserActions::DeleteAnnouncement, + UserActions::UploadAllFiles, + UserActions::ModifyAllFiles, + UserActions::DeleteAllFiles, + UserActions::ManageUserInfo, + UserActions::ManageChapterMembers, + UserActions::AssignChapterOfficer, + ]); + return [ + // Super admin gets everything + UserTypes::SUPER_ADMIN->value => UserActions::cases(), - UserTypes::ULTIMATE->value => UserActions::cases(), - - // UserTypes::ULTIMATE->value => - // [ - // UserActions::CreateUser, - // UserActions::ChangeUserPassword, - // UserActions::ModifyUser, - // UserActions::DeleteUser, - // UserActions::SetActiveUser, - // UserActions::SetInActiveUser, - // UserActions::DeveloperConsole, - // UserActions::UltimateConsole, - // UserActions::UltimateReports, - // UserActions::UltimateLogs, - // UserActions::UpdateSelfExec, - // UserActions::UpdateUserExec, - // UserActions::ViewUserExec, - // UserActions::ViewSelfExec, - // UserActions::SetUserNotes, - // UserActions::DeleteUserNotes, - // UserActions::ViewUserNotes, - // UserActions::SetSelfNotes, - // UserActions::DeleteSelfNotes, - // UserActions::ViewGlobalReports, - // UserActions::ModifyGlobalReports, - // UserActions::DeleteGlobalReports, - // UserActions::DeleteUserExec, - // UserActions::DirectUserCreditTransfer, - // UserActions::UserAllowedtoViewOtherUserRoles, - // UserActions::UserAllowedtoViewAllRoles, - // UserActions::UserAllowedtoChangeAnotherUserRoles, - // UserActions::ChangeAnotherUsersParent, - // UserActions::ViewAllUserTypes, - // UserActions::ListAllUsersAsParentforUserCreation, - // UserActions::CheckifMobileNumberExists, - // UserActions::CheckifUsernameExists, - // ], - - UserTypes::SUPER_OPERATOR->value => [ - UserActions::CreateUserOperator, - UserActions::CreateUserCoordinator, - UserActions::CreateUserSupplierOverseer, - UserActions::CreateUserWholesaleBuyer, - UserActions::CreateUserSupplier, - UserActions::CreateUserStoreOwner, - UserActions::CreateUserStoreManager, - UserActions::CreateUserUser, - UserActions::CreateUserRider, - UserActions::CreateUserPOSTerminal, - UserActions::ModifyUser, - UserActions::SetActiveUser, - UserActions::SetInActiveUser, - UserActions::DeveloperConsole, + // Punong Barangay - full management + UserTypes::PUNONG_BARANGAY->value => array_merge($staffManageActions, [ + UserActions::UltimateConsole, + UserActions::UltimateLogs, + UserActions::UltimateMaintenance, + UserActions::UltimateGlobalMessage, + UserActions::UltimateFlush, + UserActions::UltimateReports, + UserActions::ManageQrphPaymentCode, + UserActions::ManageLandingPages, + UserActions::ModifyGlobalReports, UserActions::ViewGlobalReports, - UserActions::ViewFarmers, - UserActions::ViewOrganizations, - UserActions::ViewShipments, - UserActions::CreateShipment, - UserActions::UpdateShipmentStatus, - UserActions::ViewCouriers, - UserActions::ViewGlobalTransactions, UserActions::ViewAccountingReports, UserActions::ManageAccounting, - UserActions::ViewProperties, - UserActions::ViewReferrals, - UserActions::ViewPosAccessKeys, - UserActions::ViewPosReports, - UserActions::ViewCustomers, - UserActions::ViewAllUserTypes, - UserActions::ListAllUsersAsParentforUserCreation, - UserActions::CheckifMobileNumberExists, - UserActions::CheckifUsernameExists, - UserActions::ManageLandingPages, - UserActions::JoinCooperative, - UserActions::ViewAllStores, - UserActions::CreateStoreGlobal, - UserActions::ModifyAllStores, - UserActions::ViewAllProducts, - UserActions::CreateProductGlobal, - UserActions::ModifyAllProducts, - UserActions::AddProducttoAnyStore, - UserActions::RemoveProductfromAnyStore, - UserActions::CreatePosAccessKey, - UserActions::DeletePosAccessKey, - UserActions::TogglePosAccessKey, - UserActions::SearchStockPhotos, - UserActions::DownloadStockPhoto, - ], - - UserTypes::OPERATOR->value => [ - UserActions::CreateUserCoordinator, - UserActions::CreateUserSupplier, - UserActions::CreateUserStoreOwner, - UserActions::CreateUserRider, - UserActions::CreateUserPOSTerminal, - UserActions::ModifyUser, - UserActions::SetActiveUser, - UserActions::SetInActiveUser, - UserActions::ViewShipments, - UserActions::CreateShipment, - UserActions::UpdateShipmentStatus, - UserActions::ViewPosReports, - UserActions::ViewCustomers, - UserActions::ViewUserInfo, - UserActions::ManageUserInfo, - UserActions::JoinCooperative, - UserActions::ViewOrganizations, - UserActions::ViewFarmers, - UserActions::ViewAllUserTypes, - UserActions::ListAllUsersAsParentforUserCreation, - UserActions::CheckifMobileNumberExists, - UserActions::CheckifUsernameExists, - UserActions::ManageLandingPages, - UserActions::ViewAllStores, - UserActions::CreateStoreGlobal, - UserActions::ModifyAllStores, - UserActions::ViewAllProducts, - UserActions::CreateProductGlobal, - UserActions::ModifyAllProducts, - UserActions::AddProducttoAnyStore, - UserActions::RemoveProductfromAnyStore, - UserActions::ViewPosAccessKeys, - UserActions::CreatePosAccessKey, - UserActions::DeletePosAccessKey, - UserActions::TogglePosAccessKey, - UserActions::ViewAccountingReports, - UserActions::ManageAccounting, - UserActions::ViewProperties, - UserActions::ViewReferrals, - UserActions::SearchStockPhotos, - UserActions::DownloadStockPhoto, - ], - - UserTypes::USER->value => [ - UserActions::JoinCooperative, - UserActions::ViewUserInfo, - UserActions::ManageUserInfo, - ], - - UserTypes::COOP_MEMBER->value => [ - UserActions::JoinCooperative, - UserActions::ViewUserInfo, - UserActions::ManageUserInfo, - UserActions::ViewChapterOrgChart, - ], - - UserTypes::COOP_OFFICER->value => [ - UserActions::JoinCooperative, - UserActions::ViewUserInfo, - UserActions::ManageUserInfo, - UserActions::ViewOrganizations, - UserActions::ViewChapterOrgChart, - UserActions::ManageChapterMembers, UserActions::ViewScopedMemberReports, - UserActions::AssignChapterOfficer, - UserActions::ViewAccountingReports, - UserActions::CheckifMobileNumberExists, - UserActions::CheckifUsernameExists, - ], - - UserTypes::COORDINATOR->value => [ - UserActions::ViewOrganizations, - UserActions::ViewFarmers, - UserActions::ViewShipments, - UserActions::JoinCooperative, - UserActions::ViewAccountingReports, - UserActions::CreateUserSupplier, - UserActions::CreateUserStoreManager, - UserActions::CreateUserRider, + UserActions::CreateUser, + UserActions::ModifyUser, + UserActions::SetActiveUser, + UserActions::SetInActiveUser, + UserActions::ChangeUserPassword, + UserActions::ForceLogoutUser, + UserActions::UserAllowedtoChangeAnotherUserRoles, + UserActions::UserAllowedtoViewOtherUserRoles, + UserActions::UserAllowedtoViewAllRoles, + UserActions::ChangeAnotherUsersParent, UserActions::ViewAllUserTypes, - UserActions::ListAllUsersAsParentforUserCreation, - UserActions::CheckifMobileNumberExists, - UserActions::CheckifUsernameExists, - UserActions::ManageLandingPages, - ], + UserActions::CreateUserSecretary, + UserActions::CreateUserTreasurer, + UserActions::CreateUserKagawad, + UserActions::CreateUserSkChairperson, + UserActions::CreateUserSkCouncilor, + UserActions::CreateUserTanod, + UserActions::CreateUserBhw, + UserActions::CreateUserDaycareWorker, + UserActions::CreateUserStaff, + UserActions::CreateUserResident, + UserActions::CreateUserAudit, + ]), - UserTypes::STORE_OWNER->value => [ - UserActions::CreateUserStoreManager, - UserActions::CreateUserRider, - UserActions::CreateUserPOSTerminal, - UserActions::ViewUserInfo, - UserActions::ManageUserInfo, - UserActions::ViewShipments, - UserActions::ViewPosReports, - UserActions::ViewPosAccessKeys, - UserActions::CreatePosAccessKey, - UserActions::DeletePosAccessKey, - UserActions::TogglePosAccessKey, - UserActions::JoinCooperative, + // Kagawad - manage operations, limited admin + UserTypes::KAGAWAD->value => array_merge($staffManageActions, [ + UserActions::ViewGlobalReports, + UserActions::ViewAccountingReports, + UserActions::ViewScopedMemberReports, + UserActions::CreateUserResident, + ]), + + // Secretary - records & documents focus + UserTypes::SECRETARY->value => array_merge($staffManageActions, [ + UserActions::ViewGlobalReports, + UserActions::CreateUserResident, + UserActions::ModifyUser, + UserActions::SetActiveUser, + UserActions::SetInActiveUser, + ]), + + // Treasurer - budget & payments focus + UserTypes::TREASURER->value => array_merge($staffReadActions, [ + UserActions::ViewBarangayBudget, + UserActions::ManageBarangayBudget, + UserActions::ViewFeePayments, + UserActions::ManageFeePayments, + UserActions::ViewDocumentRequests, + UserActions::ProcessDocumentRequest, UserActions::ViewAccountingReports, UserActions::ManageAccounting, - UserActions::ViewGlobalReports, - UserActions::ViewGlobalTransactions, - UserActions::SearchStockPhotos, - UserActions::DownloadStockPhoto, - ], + UserActions::ManageQrphPaymentCode, + UserActions::UploadAllFiles, + UserActions::ModifyAllFiles, + ]), - UserTypes::STORE_MANAGER->value => [ - UserActions::CreateUserRider, - UserActions::CreateUserPOSTerminal, - UserActions::ViewUserInfo, - UserActions::ManageUserInfo, - UserActions::ViewShipments, - UserActions::ViewPosReports, - UserActions::ViewPosAccessKeys, - UserActions::CreatePosAccessKey, - UserActions::DeletePosAccessKey, - UserActions::TogglePosAccessKey, - UserActions::JoinCooperative, - UserActions::ViewAccountingReports, - UserActions::ManageAccounting, - UserActions::ViewGlobalReports, - UserActions::ViewGlobalTransactions, - UserActions::CreateProductForOwnStore, - UserActions::AddProducttoOwnStore, - UserActions::SearchStockPhotos, - UserActions::DownloadStockPhoto, - ], + // SK Chairperson - youth & projects + UserTypes::SK_CHAIRPERSON->value => array_merge($staffReadActions, [ + UserActions::ManageBarangayProjects, + UserActions::CreateAnnouncement, + UserActions::ModifyAnnouncement, + UserActions::UploadAllFiles, + UserActions::CreateUserSkCouncilor, + UserActions::CreateUserResident, + ]), - UserTypes::SUPPLIER_OVERSEER->value => [ - UserActions::CreateUserSupplier, - UserActions::CreateUserWholesaleBuyer, - UserActions::CreateUserRider, - UserActions::ViewUserInfo, - UserActions::ManageUserInfo, - ], + // SK Councilor - limited support + UserTypes::SK_COUNCILOR->value => array_merge($staffReadActions, [ + UserActions::CreateAnnouncement, + UserActions::UploadAllFiles, + ]), - UserTypes::SUPPLIER->value => [ - UserActions::CreateUserRider, - UserActions::ViewUserInfo, - UserActions::ManageUserInfo, - UserActions::JoinCooperative, - ], + // Tanod - blotter & security + UserTypes::TANOD->value => array_merge($staffReadActions, [ + UserActions::CreateBlotter, + UserActions::ProcessBlotter, + UserActions::ManageBlotterHearings, + UserActions::UploadAllFiles, + ]), - UserTypes::RIDER->value => [ - UserActions::ViewShipments, - UserActions::UpdateShipmentStatus, - UserActions::ViewUserInfo, - UserActions::ManageUserInfo, - ], + // BHW - residents & health + UserTypes::BHW->value => array_merge($staffReadActions, [ + UserActions::ManageResidents, + UserActions::ManageHouseholds, + UserActions::UploadAllFiles, + ]), - UserTypes::POS_TERMINAL->value => [ - UserActions::ViewPosReports, - UserActions::ViewCustomers, - UserActions::ViewUserInfo, - UserActions::ManageUserInfo, - UserActions::ViewShipments, - ], + // Daycare Worker - basic read + residents + UserTypes::DAYCARE_WORKER->value => array_merge($staffReadActions, [ + UserActions::ManageResidents, + UserActions::UploadAllFiles, + ]), - UserTypes::AUDIT->value => [ - UserActions::ViewGlobalReports, - UserActions::ViewAllStores, - UserActions::ViewAllProducts, + // Staff - general operations + UserTypes::STAFF->value => array_merge($staffReadActions, [ + UserActions::ProcessDocumentRequest, + UserActions::CreateBlotter, + UserActions::UploadAllFiles, + UserActions::CreateUserResident, + ]), + + // Resident - self-service only + UserTypes::RESIDENT->value => [ + UserActions::CreateDocumentRequest, + UserActions::ViewDocumentRequests, UserActions::ViewAllAnnouncements, - UserActions::ViewFarmers, - UserActions::ViewOrganizations, - UserActions::ViewShipments, - UserActions::ViewCouriers, - UserActions::ViewGlobalTransactions, - UserActions::ViewAccountingReports, - UserActions::ViewProperties, - UserActions::ViewReferrals, - UserActions::ViewPosAccessKeys, - UserActions::ViewPosReports, - UserActions::ViewCustomers, - UserActions::ViewAllUserTypes, + UserActions::ViewUserInfo, + UserActions::ManageUserInfo, + UserActions::UserAllowedtoViewSelfRoles, UserActions::CheckifMobileNumberExists, UserActions::CheckifUsernameExists, + UserActions::UploadAllFiles, ], + // Audit - read-only across the system + UserTypes::AUDIT->value => array_merge($staffReadActions, [ + UserActions::ViewGlobalReports, + UserActions::ViewAccountingReports, + UserActions::ViewScopedMemberReports, + ]), ]; } } + class UserTypeService { public static function getAllowedUserTypes(UserTypes $currentUserType): array { return match ($currentUserType) { - UserTypes::ULTIMATE => UserTypes::cases(), + UserTypes::SUPER_ADMIN => UserTypes::cases(), - UserTypes::SUPER_OPERATOR => [ - UserTypes::OPERATOR, - UserTypes::COORDINATOR, - UserTypes::COOP_OFFICER, - UserTypes::COOP_MEMBER, - UserTypes::ANY_USER, - UserTypes::SUPPLIER, - UserTypes::STORE_OWNER, - UserTypes::STORE_MANAGER, - UserTypes::SUPPLIER_OVERSEER, - UserTypes::WHOLESALE_BUYER, - UserTypes::RIDER, - UserTypes::POS_TERMINAL, + UserTypes::PUNONG_BARANGAY => [ + UserTypes::KAGAWAD, + UserTypes::SECRETARY, + UserTypes::TREASURER, + UserTypes::SK_CHAIRPERSON, + UserTypes::SK_COUNCILOR, + UserTypes::TANOD, + UserTypes::BHW, + UserTypes::DAYCARE_WORKER, + UserTypes::STAFF, + UserTypes::RESIDENT, + UserTypes::AUDIT, ], - UserTypes::OPERATOR => [ - UserTypes::COORDINATOR, - UserTypes::COOP_OFFICER, - UserTypes::COOP_MEMBER, - UserTypes::SUPPLIER, - UserTypes::STORE_OWNER, - UserTypes::RIDER, - UserTypes::POS_TERMINAL, + UserTypes::KAGAWAD, UserTypes::SECRETARY => [ + UserTypes::RESIDENT, ], - UserTypes::COORDINATOR => [ - UserTypes::COOP_OFFICER, - UserTypes::COOP_MEMBER, - UserTypes::SUPPLIER, - UserTypes::STORE_MANAGER, - UserTypes::RIDER, - ], - - UserTypes::COOP_OFFICER => [ - UserTypes::COOP_MEMBER, - ], - - UserTypes::STORE_OWNER => [ - UserTypes::STORE_MANAGER, - UserTypes::RIDER, - UserTypes::POS_TERMINAL, - ], - - UserTypes::STORE_MANAGER => [ - UserTypes::RIDER, - UserTypes::POS_TERMINAL, - ], - - UserTypes::SUPPLIER => [ - UserTypes::RIDER, - ], - - UserTypes::SUPPLIER_OVERSEER => [ - UserTypes::SUPPLIER, - UserTypes::WHOLESALE_BUYER, - UserTypes::RIDER, + UserTypes::SK_CHAIRPERSON => [ + UserTypes::SK_COUNCILOR, + UserTypes::RESIDENT, ], default => [], diff --git a/app/Http/Controllers/Market/ActivityController.php b/app/Http/Controllers/Market/ActivityController.php deleted file mode 100644 index 64107a9..0000000 --- a/app/Http/Controllers/Market/ActivityController.php +++ /dev/null @@ -1,55 +0,0 @@ -input('limit', 10); - $service = new ActivityService(); - $activities = $service->getRecentActivities($limit); - - return Response::json([ - 'success' => true, - 'data' => $activities - ]); - } - - /** - * Search activities. - * - * @param Request $request - * @return \Psr\Http\Message\ResponseInterface - */ - public function search(Request $request) - { - $query = $request->input('q', ''); - $limit = (int) $request->input('limit', 20); - $service = new ActivityService(); - - if (empty($query)) { - $activities = $service->getRecentActivities($limit); - } else { - $activities = $service->searchActivities($query, $limit); - } - - return Response::json([ - 'success' => true, - 'data' => $activities - ]); - } -} diff --git a/app/Http/Controllers/Market/BatchController.php b/app/Http/Controllers/Market/BatchController.php deleted file mode 100644 index 0d3405b..0000000 --- a/app/Http/Controllers/Market/BatchController.php +++ /dev/null @@ -1,577 +0,0 @@ -acct_type instanceof UserTypes ? $user->acct_type : UserTypes::tryFrom($user->acct_type); - $isBig3 = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR]); - $isStoreOwner = $acctType === UserTypes::STORE_OWNER; - - if (!$isBig3 && !$isStoreOwner) { - return ResponseHelper::returnUnauthorized(); - } - - $products = $request->input('products', []); - if (empty($products)) { - return ResponseHelper::returnError('No products provided'); - } - - $targetStoreHash = $request->input('target_store_hash'); - $targetStore = null; - if ($targetStoreHash) { - $targetStore = Store::where('hashkey', $targetStoreHash)->first(); - if (!$targetStore) { - return ResponseHelper::returnError('Target store not found'); - } - } - - // STORE_OWNER must operate against a store they actually own/manage. - // Without that, batch-import has no destination and must be refused - // up-front so the UI doesn't silently 401 partway through. - if (!$isBig3) { - if (!$targetStore) { - return ResponseHelper::returnError('You must select one of your stores before importing products.', 422); - } - $ownsTarget = (int) $targetStore->owner_id === (int) $user->id - || (int) $targetStore->manager_id === (int) $user->id - || $targetStore->managers()->where('user_id', $user->id)->exists(); - if (!$ownsTarget) { - return ResponseHelper::returnError('You can only import products into stores you own.', 403); - } - } - - $results = []; - $errors = []; - - DB::beginTransaction(); - try { - foreach ($products as $index => $productData) { - $source = $productData['source'] ?? 'new'; - - if ($source === 'existing') { - if (!$targetStore) { - $errors[] = 'Row ' . ($index + 1) . ': A target store is required to import an existing product.'; - continue; - } - - $validator = Validator::make($productData, [ - 'product_hash' => 'required|string', - 'price' => 'nullable|numeric|min:0', - 'available' => 'nullable|numeric', - 'description' => 'nullable|string', - ]); - - if ($validator->fails()) { - $errors[] = 'Row ' . ($index + 1) . ': ' . implode(', ', $validator->errors()->all()); - continue; - } - - $existing = Product::where('hashkey', $productData['product_hash'])->first(); - if (!$existing) { - $errors[] = 'Row ' . ($index + 1) . ': Selected global product not found.'; - continue; - } - - $alreadyAttached = $targetStore->products()->where('prd_items.id', $existing->id)->exists(); - if ($alreadyAttached) { - $errors[] = 'Row ' . ($index + 1) . ": '{$existing->name}' is already in the target store."; - continue; - } - - $price = $productData['price'] ?? null; - $price = ($price === null || $price === '' || (float) $price <= 0) - ? (float) $existing->price - : (float) $price; - - $description = $productData['description'] ?? ''; - if (trim((string) $description) === '') { - $description = (string) ($existing->description ?? ''); - } - - $available = (int) ($productData['available'] ?? 0); - - $targetStore->products()->attach($existing->id, [ - 'available' => $available, - 'price' => $price, - 'description' => $description, - 'is_active' => true, - ]); - - $results[] = $existing->hashkey; - continue; - } - - $validator = Validator::make($productData, [ - 'name' => 'required|string|max:255', - 'price' => 'required|numeric|min:0', - 'available' => 'required|numeric', - 'unitname' => 'required|string|max:100', - 'description' => 'nullable|string', - 'category' => 'nullable|string|max:255', - 'subcategory' => 'nullable|string|max:255', - 'barcode' => 'nullable|string|max:255', - 'photourl' => 'nullable|array', - 'photourl.*' => 'nullable|string', - ]); - - if ($validator->fails()) { - $errors[] = "Row " . ($index + 1) . ": " . implode(', ', $validator->errors()->all()); - continue; - } - - // Reject if a global product with this name already exists. - // Owners should pick the existing one via the fuzzy-search - // modal instead of creating a duplicate global entry. - $duplicate = Product::whereRaw('LOWER(TRIM(name)) = ?', [strtolower(trim((string) $productData['name']))]) - ->first(); - if ($duplicate) { - $errors[] = "Row " . ($index + 1) . ": '{$productData['name']}' already exists globally. Use 'Pick existing' to import it instead."; - continue; - } - - $product = Product::create([ - 'name' => $productData['name'], - 'description' => $productData['description'] ?? '', - 'price' => $productData['price'], - 'unitname' => $productData['unitname'], - 'available' => $productData['available'], - 'barcode' => $productData['barcode'] ?? null, - 'category' => $productData['category'] ?? null, - 'subcategory' => $productData['subcategory'] ?? null, - 'photourl' => $productData['photourl'] ?? [], - 'created_by' => $user->id, - 'is_active' => true, - ]); - - if ($targetStore) { - $targetStore->products()->attach($product->id, [ - 'available' => (int) $productData['available'], - 'price' => (float) $productData['price'], - 'description' => $productData['description'] ?? '', - 'is_active' => true, - ]); - } - - $results[] = $product->hashkey; - } - - if (!empty($errors)) { - DB::rollBack(); - return Response::json(['success' => false, 'message' => 'Batch creation failed', 'errors' => $errors], 422); - } - - DB::commit(); - return Response::json(['success' => true, 'count' => count($results), 'data' => $results]); - } catch (\Throwable $th) { - DB::rollBack(); - return ResponseHelper::returnError($th->getMessage()); - } - } - - /** - * Serve the batch products Excel template as a file download. - * Available for all authenticated users with batch module access. - */ - public function downloadProductTemplate() - { - $templatePath = BASE_PATH . '/resources/templates/batch-products-template.xlsx'; - - if (!file_exists($templatePath)) { - return Response::json(['success' => false, 'message' => 'Template file not found.'], 404); - } - - $fileContent = file_get_contents($templatePath); - $filename = 'bukidbounty-batch-products-template.xlsx'; - - return Response::make($fileContent, 200, [ - 'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'Content-Disposition' => 'attachment; filename="' . $filename . '"', - 'Content-Length' => strlen($fileContent), - 'Cache-Control' => 'no-cache, no-store, must-revalidate', - 'Pragma' => 'no-cache', - 'Expires' => '0', - ]); - } - - /** - * Batch create stores. - * Available for Ultimate, Super Operator, and Operator. - */ - public function batchCreateStores(Request $request) - { - $user = Auth::user(); - if (!$user) return ResponseHelper::returnUnauthorized(); - - $acctType = $user->acct_type instanceof UserTypes ? $user->acct_type : UserTypes::tryFrom($user->acct_type); - $isBig3 = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR]); - - if (!$isBig3) { - return ResponseHelper::returnUnauthorized(); - } - - $stores = $request->input('stores', []); - if (empty($stores)) { - return ResponseHelper::returnError('No stores provided'); - } - - $results = []; - $errors = []; - - DB::beginTransaction(); - try { - foreach ($stores as $index => $storeData) { - $validator = Validator::make($storeData, [ - 'name' => 'required|string|max:255', - 'description' => 'required|string', - 'address' => 'required|string', - 'category' => 'nullable|string|max:100', - 'subcategory' => 'nullable|string|max:100', - 'owner_hash' => 'nullable|string', - ]); - - if ($validator->fails()) { - $errors[] = "Row " . ($index + 1) . ": " . implode(', ', $validator->errors()->all()); - continue; - } - - $ownerId = null; - if (!empty($storeData['owner_hash'])) { - $ownerId = UserController::findUserIdByHash($storeData['owner_hash']); - } - - $storeCode = StoreController::generateStoreCode($storeData['category'] ?? 'General'); - - $store = Store::create([ - 'storecode' => $storeCode, - 'name' => $storeData['name'], - 'description' => $storeData['description'], - 'address' => $storeData['address'], - 'category' => $storeData['category'] ?? 'General', - 'subcategory' => $storeData['subcategory'] ?? '', - 'owner_id' => $ownerId, - 'created_by' => $user->id, - 'is_active' => true, - 'status' => 'active', - ]); - - $results[] = $store->hashkey; - } - - if (!empty($errors)) { - DB::rollBack(); - return Response::json(['success' => false, 'message' => 'Batch creation failed', 'errors' => $errors], 422); - } - - DB::commit(); - return Response::json(['success' => true, 'count' => count($results), 'data' => $results]); - } catch (\Throwable $th) { - DB::rollBack(); - return ResponseHelper::returnError($th->getMessage()); - } - } - - /** - * Batch create users. - * Available for Ultimate, Super Operator, and Operator. - */ - public function batchCreateUsers(Request $request) - { - $user = Auth::user(); - if (!$user) return ResponseHelper::returnUnauthorized(); - - $acctType = $user->acct_type instanceof UserTypes ? $user->acct_type : UserTypes::tryFrom($user->acct_type); - $isBig3 = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR]); - - if (!$isBig3) { - return ResponseHelper::returnUnauthorized(); - } - - $usersData = $request->input('users', []); - if (empty($usersData)) { - return ResponseHelper::returnError('No users provided'); - } - - $results = []; - $errors = []; - - DB::beginTransaction(); - try { - foreach ($usersData as $index => $data) { - $validator = Validator::make($data, [ - 'username' => 'required|string|max:255|unique:users,username', - 'name' => 'required|string|max:255', - 'mobile_number' => ['required', 'string', 'max:20', 'unique:users,mobile_number', 'regex:/^(09|\+639)\d{9}$/'], - 'password' => 'required|string|min:6', - 'type' => 'required|string', - 'parent_hash' => 'nullable|string', - ]); - - if ($validator->fails()) { - $errors[] = "Row " . ($index + 1) . ": " . implode(', ', $validator->errors()->all()); - continue; - } - - $usertypeEnum = UserTypes::tryFrom($data['type']); - if (!$usertypeEnum) { - $errors[] = "Row " . ($index + 1) . ": Invalid User Type"; - continue; - } - - // Check if creator is allowed to create this user type - if ($acctType !== UserTypes::ULTIMATE) { - $allowedTypes = UserTypeService::getAllowedUserTypes($acctType); - if (!in_array($usertypeEnum, $allowedTypes)) { - $errors[] = "Row " . ($index + 1) . ": You are not allowed to create user type '{$data['type']}'"; - continue; - } - } - - $parentId = $user->id; // Default to creator - if (!empty($data['parent_hash'])) { - $parent = User::where('hashkey', $data['parent_hash'])->first(); - if ($parent) { - $parentId = $parent->id; - } - } - - $newUser = User::create([ - 'username' => $data['username'], - 'name' => $data['name'], - 'mobile_number' => $data['mobile_number'], - 'password' => Hash::make($data['password']), - 'acct_type' => $data['type'], - 'parentuid' => $parentId, - 'active' => true, - ]); - - $results[] = $newUser->hashkey; - } - - if (!empty($errors)) { - DB::rollBack(); - return Response::json(['success' => false, 'message' => 'Batch creation failed', 'errors' => $errors], 422); - } - - DB::commit(); - return Response::json(['success' => true, 'count' => count($results), 'data' => $results]); - } catch (\Throwable $th) { - DB::rollBack(); - return ResponseHelper::returnError($th->getMessage()); - } - } - - /** - * Batch create cooperatives. - * Available for Ultimate and Super Operator. - */ - public function batchCreateCooperatives(Request $request) - { - $user = Auth::user(); - if (!$user) return ResponseHelper::returnUnauthorized(); - - $acctType = $user->acct_type instanceof UserTypes ? $user->acct_type : UserTypes::tryFrom($user->acct_type); - $isAllowed = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR]); - - if (!$isAllowed) { - return ResponseHelper::returnUnauthorized(); - } - - $cooperatives = $request->input('cooperatives', []); - if (empty($cooperatives)) { - return ResponseHelper::returnError('No cooperatives provided'); - } - - $results = []; - $errors = []; - - DB::beginTransaction(); - try { - foreach ($cooperatives as $index => $data) { - $validator = Validator::make($data, [ - 'name' => 'required|string|max:255', - 'address' => 'nullable|string', - 'registration_number' => 'nullable|string|max:255', - 'cin' => 'nullable|string|max:255', - 'tin' => 'nullable|string|max:255', - 'cooperative_type' => 'nullable|string|max:100', - 'cooperative_category' => 'nullable|string|max:100', - 'registration_date' => 'nullable|date', - 'contact_person' => 'nullable|string|max:255', - 'contact_number' => 'nullable|string|max:50', - 'contact_email' => 'nullable|email|max:255', - ]); - - if ($validator->fails()) { - $errors[] = "Row " . ($index + 1) . ": " . implode(', ', $validator->errors()->all()); - continue; - } - - $cooperative = new Organization([ - 'hashkey' => Str::random(64), - 'name' => trim($data['name']), - 'type' => 'COOPERATIVE', - 'address' => trim($data['address'] ?? ''), - 'registration_number' => trim($data['registration_number'] ?? ''), - 'cin' => trim($data['cin'] ?? ''), - 'tin' => trim($data['tin'] ?? ''), - 'cooperative_type' => trim($data['cooperative_type'] ?? ''), - 'cooperative_category' => trim($data['cooperative_category'] ?? ''), - 'registration_date' => $data['registration_date'] ?? null, - 'contact_person' => trim($data['contact_person'] ?? ''), - 'contact_number' => trim($data['contact_number'] ?? ''), - 'contact_email' => trim($data['contact_email'] ?? ''), - 'is_active' => true, - 'created_by' => $user->id, - ]); - - if (!$cooperative->save()) { - $errors[] = "Row " . ($index + 1) . ": Failed to save cooperative"; - continue; - } - - $results[] = $cooperative->hashkey; - } - - if (!empty($errors)) { - DB::rollBack(); - return Response::json(['success' => false, 'message' => 'Batch creation failed', 'errors' => $errors], 422); - } - - DB::commit(); - return Response::json(['success' => true, 'count' => count($results), 'data' => $results]); - } catch (\Throwable $th) { - DB::rollBack(); - return ResponseHelper::returnError($th->getMessage()); - } - } - - /** - * Batch add cooperative members with automatic user account creation. - * Each row creates a new User and a corresponding CooperativeMember in one transaction. - */ - public function batchCreateCooperativeMembers(Request $request) - { - $user = Auth::user(); - if (!$user) return ResponseHelper::returnUnauthorized(); - - if (!UserPermissions::isActionPermitted($user->acct_type, UserActions::ManageOrganizations)) { - return ResponseHelper::returnUnauthorized(); - } - - $cooperativeHash = $request->input('cooperative_hash'); - $members = $request->input('members', []); - - if (!$cooperativeHash) { - return ResponseHelper::returnError('Cooperative hash is required'); - } - if (empty($members)) { - return ResponseHelper::returnError('No members provided'); - } - - $cooperative = Organization::where('hashkey', $cooperativeHash) - ->where('type', 'COOPERATIVE') - ->first(); - - if (!$cooperative) { - return ResponseHelper::returnError('Cooperative not found', 404); - } - - $parentUser = User::where('id', $cooperative->created_by)->first() ?? $user; - - $results = []; - $errors = []; - - DB::beginTransaction(); - try { - foreach ($members as $index => $data) { - $validator = Validator::make($data, [ - 'username' => 'required|string|max:255|unique:users,username', - 'name' => 'required|string|max:255', - 'mobile_number' => ['required', 'string', 'max:20', 'unique:users,mobile_number', 'regex:/^(09|\+639)\d{9}$/'], - 'password' => 'required|string|min:6', - 'role' => 'nullable|string|max:50', - 'membership_type' => 'nullable|string|max:50', - ]); - - if ($validator->fails()) { - $errors[] = 'Row ' . ($index + 1) . ': ' . implode(', ', $validator->errors()->all()); - continue; - } - - $newUser = new User(); - $newUser->username = $data['username']; - $newUser->name = $data['name']; - $newUser->mobile_number = $data['mobile_number']; - $newUser->password = Hash::make($data['password']); - $newUser->parentuid = $parentUser->id; - $newUser->acct_type = 'user'; - $newUser->active = true; - $newUser->save(); - - $member = new CooperativeMember([ - 'hashkey' => Str::random(64), - 'organization_id' => $cooperative->id, - 'user_id' => $newUser->id, - 'role' => $data['role'] ?? 'MEMBER', - 'membership_type' => $data['membership_type'] ?? null, - 'joined_at' => now(), - 'is_active' => true, - 'created_by' => $user->id, - ]); - - if (!$member->save()) { - $errors[] = 'Row ' . ($index + 1) . ': Failed to save membership'; - continue; - } - - $results[] = [ - 'user_hashkey' => $newUser->hashkey, - 'member_hashkey' => $member->hashkey, - ]; - } - - if (!empty($errors)) { - DB::rollBack(); - return Response::json(['success' => false, 'message' => 'Batch creation failed', 'errors' => $errors], 422); - } - - DB::commit(); - return Response::json(['success' => true, 'count' => count($results), 'data' => $results]); - } catch (\Throwable $th) { - DB::rollBack(); - return ResponseHelper::returnError($th->getMessage()); - } - } -} diff --git a/app/Http/Controllers/Market/CartController.php b/app/Http/Controllers/Market/CartController.php deleted file mode 100644 index b8e0b21..0000000 --- a/app/Http/Controllers/Market/CartController.php +++ /dev/null @@ -1,142 +0,0 @@ - 'Unauthorized'], 401); - } - - $cart = Cart::firstOrCreate(['user_id' => $user->id]); - - $items = $cart->items()->with('product')->get(); - - return Response::json([ - 'success' => true, - 'cart' => $cart, - 'items' => $items, - 'total' => $items->sum(fn($item) => $item->price * $item->quantity) - ]); - } - - public function addItem(Request $request) - { - $user = Auth::user(); - if (!$user) { - return Response::json(['error' => 'Unauthorized'], 401); - } - - $request->validate([ - 'product_hash' => 'required|string', - 'quantity' => 'nullable|integer|min:1', - ]); - - $product = Product::where('hashkey', $request->input('product_hash'))->first(); - if (!$product) { - return Response::json(['error' => 'Product not found'], 404); - } - - $cart = Cart::firstOrCreate(['user_id' => $user->id]); - - $item = $cart->items()->where('product_id', $product->id)->first(); - - if ($item) { - $item->quantity += $request->input('quantity', 1); - $item->save(); - } else { - $cart->items()->create([ - 'product_id' => $product->id, - 'quantity' => $request->input('quantity', 1), - 'price' => $product->price, - 'is_active' => true, - 'hashkey' => Str::uuid()->toString(), - ]); - } - - return Response::json(['success' => true, 'message' => 'Item added to cart']); - } - - public function updateItem(Request $request) - { - $user = Auth::user(); - if (!$user) { - return Response::json(['error' => 'Unauthorized'], 401); - } - - $request->validate([ - 'item_hash' => 'required|string', - 'quantity' => 'required|integer|min:1', - ]); - - $item = CartItem::where('hashkey', $request->input('item_hash'))->first(); - if (!$item) { - return Response::json(['error' => 'Item not found'], 404); - } - - // Verify cart ownership - $cart = Cart::find($item->cart_id); - if ($cart->user_id !== $user->id) { - return Response::json(['error' => 'Forbidden'], 403); - } - - $item->quantity = $request->input('quantity'); - $item->save(); - - return Response::json(['success' => true, 'message' => 'Cart updated']); - } - - public function removeItem(Request $request) - { - $user = Auth::user(); - if (!$user) { - return Response::json(['error' => 'Unauthorized'], 401); - } - - $request->validate([ - 'item_hash' => 'required|string', - ]); - - $item = CartItem::where('hashkey', $request->input('item_hash'))->first(); - if (!$item) { - return Response::json(['error' => 'Item not found'], 404); - } - - $cart = Cart::find($item->cart_id); - if ($cart->user_id !== $user->id) { - return Response::json(['error' => 'Forbidden'], 403); - } - - $item->delete(); - - return Response::json(['success' => true, 'message' => 'Item removed from cart']); - } - - public function clearCart() - { - $user = Auth::user(); - if (!$user) { - return Response::json(['error' => 'Unauthorized'], 401); - } - - $cart = Cart::where('user_id', $user->id)->first(); - if ($cart) { - $cart->items()->delete(); - } - - return Response::json(['success' => true, 'message' => 'Cart cleared']); - } -} diff --git a/app/Http/Controllers/Market/CooperativeController.php b/app/Http/Controllers/Market/CooperativeController.php deleted file mode 100644 index 200c48a..0000000 --- a/app/Http/Controllers/Market/CooperativeController.php +++ /dev/null @@ -1,470 +0,0 @@ -acct_type, UserActions::ViewOrganizations)) { - return ResponseHelper::returnUnauthorized(); - } - - $query = Organization::where('is_active', true)->where('type', 'COOPERATIVE'); - - $cooperatives = $query->withCount('members')->get(); - - return response()->json([ - 'success' => true, - 'data' => $cooperatives - ]); - } - - public function getCooperative(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewOrganizations)) { - return ResponseHelper::returnUnauthorized(); - } - - $hashkey = $request->input('hashkey'); - if (!$hashkey) { - return ResponseHelper::returnIncorrectDetails(); - } - - $cooperative = Organization::where('hashkey', $hashkey)->with(['members.user.userInfo'])->first(); - - if (!$cooperative) { - return ResponseHelper::returnError('Cooperative not found', 404); - } - - $currentUserMembership = null; - $user = Auth::user(); - if ($user) { - $currentUserMembership = CooperativeMember::where('organization_id', $cooperative->id) - ->where('user_id', $user->id) - ->first(); - } - - return response()->json([ - 'success' => true, - 'data' => $cooperative, - 'is_member' => $currentUserMembership !== null, - 'membership' => $currentUserMembership, - ]); - } - - public function createCooperative(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::CreateOrganization)) { - return ResponseHelper::returnUnauthorized(); - } - - $name = $request->input('name'); - $address = $request->input('address', ''); - $registrationNumber = $request->input('registration_number', ''); - $cin = $request->input('cin', ''); - $tin = $request->input('tin', ''); - $cooperativeType = $request->input('cooperative_type', ''); - $cooperativeCategory = $request->input('cooperative_category', ''); - $registrationDate = $request->input('registration_date', null); - $contactPerson = $request->input('contact_person', ''); - $contactNumber = $request->input('contact_number', ''); - $contactEmail = $request->input('contact_email', ''); - - if (empty(trim($name ?? ''))) { - return ResponseHelper::returnError('Cooperative name is required'); - } - - $cooperative = new Organization([ - 'hashkey' => Str::random(64), - 'name' => trim($name), - 'type' => 'COOPERATIVE', - 'address' => trim($address ?? ''), - 'registration_number' => trim($registrationNumber), - 'cin' => trim($cin), - 'tin' => trim($tin), - 'cooperative_type' => trim($cooperativeType), - 'cooperative_category' => trim($cooperativeCategory), - 'registration_date' => $registrationDate, - 'contact_person' => trim($contactPerson), - 'contact_number' => trim($contactNumber), - 'contact_email' => trim($contactEmail), - 'is_active' => true, - 'created_by' => Auth::id(), - ]); - - if ($cooperative->save()) { - return ResponseHelper::returnSuccessResponse($cooperative, $cooperative->hashkey, 'Cooperative created successfully'); - } - - return ResponseHelper::returnError('Failed to create cooperative'); - } - - public function joinCooperative(Request $request) - { - $user = Auth::user(); - if (!$user) { - return ResponseHelper::returnUnauthorized(); - } - - $cooperativeHash = $request->input('cooperative_hash'); - if (!$cooperativeHash) { - return ResponseHelper::returnIncorrectDetails(); - } - - $cooperative = Organization::where('hashkey', $cooperativeHash)->first(); - if (!$cooperative) { - return ResponseHelper::returnError('Cooperative not found', 404); - } - - // Check if already a member - $existing = CooperativeMember::where('organization_id', $cooperative->id) - ->where('user_id', $user->id) - ->first(); - - if ($existing) { - return ResponseHelper::returnError('Already a member of this cooperative'); - } - - $memberFields = $request->only([ - 'role', 'membership_type', 'membership_level', 'officer_position', 'officer_level', - 'concurrent_position', 'concurrent_level', 'cooperative_name_alt', 'cooperative_position', 'year_beginning', - 'priority_sector', 'common_bond', 'vulnerability_classifications', - 'philsys_id', 'sss_number', 'pagibig_number', - 'slp_track', 'slp_association_name', 'listahanan_id', 'fourtps_household_id', - 'tupad_category', 'tupad_insurance_beneficiary_name', 'tupad_insurance_beneficiary_relation', - 'preferred_occupation', 'nsrp_skills', 'employment_status', 'program_participation', - ]); - - $member = new CooperativeMember(array_merge($memberFields, [ - 'organization_id' => $cooperative->id, - 'user_id' => $user->id, - 'role' => $request->input('role', 'MEMBER'), - 'joined_at' => now(), - 'is_active' => true, - ])); - - if ($member->save()) { - // Sync with user settings - $settings = $user->settings ?? []; - $cooperatives = $settings['cooperatives'] ?? []; - if (!in_array($cooperativeHash, $cooperatives)) { - $cooperatives[] = $cooperativeHash; - $settings['cooperatives'] = $cooperatives; - $user->settings = $settings; - $user->save(); - } - return ResponseHelper::returnSuccessResponse($member, $member->hashkey, 'Successfully joined cooperative'); - } - - return ResponseHelper::returnError('Failed to join cooperative'); - } - - public function addMember(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageOrganizations)) { - return ResponseHelper::returnUnauthorized(); - } - - $cooperativeHash = $request->input('cooperative_hash'); - $userHash = $request->input('user_hash'); - - if (!$cooperativeHash || !$userHash) { - return ResponseHelper::returnIncorrectDetails(); - } - - $cooperative = Organization::where('hashkey', $cooperativeHash)->first(); - $targetUser = User::where('hashkey', $userHash)->first(); - - if (!$cooperative || !$targetUser) { - return ResponseHelper::returnError('Cooperative or User not found', 404); - } - - $existing = CooperativeMember::where('organization_id', $cooperative->id) - ->where('user_id', $targetUser->id) - ->first(); - - if ($existing) { - return ResponseHelper::returnError('User is already a member'); - } - - $memberFields = $request->only([ - 'role', 'membership_type', 'membership_level', 'officer_position', 'officer_level', - 'concurrent_position', 'concurrent_level', 'cooperative_name_alt', 'cooperative_position', 'year_beginning', - 'priority_sector', 'common_bond', 'vulnerability_classifications', - 'philsys_id', 'sss_number', 'pagibig_number', - 'slp_track', 'slp_association_name', 'listahanan_id', 'fourtps_household_id', - 'tupad_category', 'tupad_insurance_beneficiary_name', 'tupad_insurance_beneficiary_relation', - 'preferred_occupation', 'nsrp_skills', 'employment_status', 'program_participation', - ]); - - $member = new CooperativeMember(array_merge($memberFields, [ - 'organization_id' => $cooperative->id, - 'user_id' => $targetUser->id, - 'role' => $request->input('role', 'MEMBER'), - 'joined_at' => now(), - 'is_active' => true, - ])); - - if ($member->save()) { - return ResponseHelper::returnSuccessResponse($member, $member->hashkey, 'Member added to cooperative'); - } - - return ResponseHelper::returnError('Failed to add member'); - } - - public function updateMember(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageOrganizations)) { - return ResponseHelper::returnUnauthorized(); - } - - $memberHash = $request->input('member_hash'); - if (!$memberHash) { - return ResponseHelper::returnIncorrectDetails(); - } - - $member = CooperativeMember::where('hashkey', $memberHash)->first(); - if (!$member) { - return ResponseHelper::returnError('Member record not found', 404); - } - - $memberFields = $request->only([ - 'role', 'membership_type', 'membership_level', 'officer_position', 'officer_level', - 'concurrent_position', 'concurrent_level', 'cooperative_name_alt', 'cooperative_position', 'year_beginning', - 'is_active' - ]); - - $member->fill($memberFields); - - if ($member->save()) { - return ResponseHelper::returnSuccessResponse($member, $member->hashkey, 'Membership details updated'); - } - - return ResponseHelper::returnError('Failed to update membership details'); - } - - public function registerMember(Request $request) - { - $user = Auth::user(); - if (!$user) { - return ResponseHelper::returnUnauthorized(); - } - - if (!UserPermissions::isActionPermitted($user->acct_type, UserActions::JoinCooperative)) { - return ResponseHelper::returnUnauthorized(); - } - - $cooperativeHash = $request->input('cooperative_hash'); - if (!$cooperativeHash) { - return ResponseHelper::returnIncorrectDetails(); - } - - $cooperative = Organization::where('hashkey', $cooperativeHash)->first(); - if (!$cooperative) { - return ResponseHelper::returnError('Cooperative not found', 404); - } - - // Check if already a member - $existing = CooperativeMember::where('organization_id', $cooperative->id) - ->where('user_id', $user->id) - ->first(); - - if ($existing) { - return ResponseHelper::returnError('Already a member of this cooperative'); - } - - $memberFields = $request->only([ - 'role', 'membership_type', 'membership_level', 'officer_position', 'officer_level', - 'concurrent_position', 'concurrent_level', 'cooperative_name_alt', 'cooperative_position', 'year_beginning', - 'priority_sector', 'common_bond', 'vulnerability_classifications', - 'philsys_id', 'sss_number', 'pagibig_number', - 'slp_track', 'slp_association_name', 'listahanan_id', 'fourtps_household_id', - 'tupad_category', 'tupad_insurance_beneficiary_name', 'tupad_insurance_beneficiary_relation', - 'preferred_occupation', 'nsrp_skills', 'employment_status', 'program_participation', - ]); - - $member = new CooperativeMember(array_merge($memberFields, [ - 'hashkey' => Str::random(64), - 'organization_id' => $cooperative->id, - 'user_id' => $user->id, - 'role' => $request->input('role', 'MEMBER'), - 'joined_at' => now(), - 'is_active' => true, - 'created_by' => $user->id, - ])); - - if ($member->save()) { - // Sync with user settings - $settings = $user->settings ?? []; - $cooperatives = $settings['cooperatives'] ?? []; - if (!is_array($cooperatives)) $cooperatives = []; - - if (!in_array($cooperativeHash, $cooperatives)) { - $cooperatives[] = $cooperativeHash; - $settings['cooperatives'] = $cooperatives; - $user->settings = $settings; - $user->save(); - } - // Upgrade a plain USER to COOP_MEMBER on cooperative registration. - // Never downgrade a higher type (COORDINATOR, OPERATOR, etc.). - if ($user->acct_type === UserTypes::USER) { - $user->acct_type = UserTypes::COOP_MEMBER; - $user->save(); - } - return ResponseHelper::returnSuccessResponse($member, $member->hashkey, 'Successfully registered as a cooperative member'); - } - - return ResponseHelper::returnError('Failed to register'); - } - - public function publicCompleteMembership(Request $request) - { - $userHashkey = $request->input('user_hashkey'); - $coopHash = $request->input('cooperative_hash'); - - if (!$userHashkey || !$coopHash) { - return ResponseHelper::returnIncorrectDetails(); - } - - $user = User::where('hashkey', $userHashkey)->first(); - if (!$user) { - return response()->json(['success' => false, 'message' => 'User not found'], 404); - } - - $cooperative = Organization::where('hashkey', $coopHash) - ->where('type', 'COOPERATIVE') - ->where('is_active', true) - ->first(); - - if (!$cooperative) { - return response()->json(['success' => false, 'message' => 'Cooperative not found'], 404); - } - - $existing = CooperativeMember::where('organization_id', $cooperative->id) - ->where('user_id', $user->id) - ->first(); - - if ($existing) { - return response()->json(['success' => false, 'message' => 'Already a member'], 409); - } - - $member = new CooperativeMember(array_merge( - $request->only([ - 'membership_type', 'membership_level', 'year_beginning', - 'officer_position', 'officer_level', 'concurrent_position', 'concurrent_level', - 'cooperative_position', 'cooperative_name_alt', - 'priority_sector', 'common_bond', 'vulnerability_classifications', - 'philsys_id', 'sss_number', 'pagibig_number', - 'slp_track', 'slp_association_name', 'listahanan_id', 'fourtps_household_id', - 'tupad_category', 'tupad_insurance_beneficiary_name', 'tupad_insurance_beneficiary_relation', - 'preferred_occupation', 'nsrp_skills', 'employment_status', 'program_participation', - ]), - [ - 'hashkey' => Str::random(64), - 'organization_id' => $cooperative->id, - 'user_id' => $user->id, - 'role' => 'MEMBER', - 'joined_at' => now(), - 'is_active' => true, - 'created_by' => $user->id, - ] - )); - $member->save(); - - $settings = $user->settings ?? []; - $settings['cooperatives'] = array_unique(array_merge($settings['cooperatives'] ?? [], [$coopHash])); - $user->settings = $settings; - $user->save(); - - return response()->json(['success' => true, 'message' => 'Membership application submitted successfully.']); - } - - public function publicGetCooperative(Request $request, string $hkey) - { - try { - $cooperative = Organization::where('hashkey', $hkey) - ->where('type', 'COOPERATIVE') - ->where('is_active', true) - ->select(['id', 'hashkey', 'name', 'type', 'cooperative_type', 'cooperative_category', 'contact_person', 'contact_number', 'address']) - ->first(); - } catch (\Throwable $e) { - return Response::json(['success' => false, 'message' => 'Service temporarily unavailable'], 500); - } - - if (!$cooperative) { - return Response::json(['success' => false, 'message' => 'Cooperative not found'], 404); - } - - return Response::json(['success' => true, 'data' => $cooperative]); - } - - public function publicRegisterMember(Request $request) - { - $hkey = $request->input('cooperative_hash'); - if (!$hkey) { - return ResponseHelper::returnIncorrectDetails(); - } - - $cooperative = Organization::where('hashkey', $hkey) - ->where('type', 'COOPERATIVE') - ->where('is_active', true) - ->first(); - - if (!$cooperative) { - return Response::json(['success' => false, 'message' => 'Cooperative not found'], 404); - } - - try { - $validated = $request->validate([ - 'name' => 'required|string|max:255', - 'username' => 'required|string|max:255|unique:users,username', - 'mobile_number' => ['required', 'string', 'max:20', 'unique:users,mobile_number', 'regex:/^(09|\+639)\d{9}$/'], - 'password' => 'required|string|min:6', - ]); - } catch (\Hypervel\Validation\ValidationException $e) { - return Response::json(['success' => false, 'errors' => $e->errors()], 422); - } - - $parentUser = User::where('id', $cooperative->created_by)->first() - ?? User::where('acct_type', 'COORDINATOR')->first() - ?? User::orderBy('id')->first(); - - if (!$parentUser) { - return Response::json(['success' => false, 'message' => 'No valid parent user found'], 500); - } - - $user = new User(); - $user->username = $validated['username']; - $user->name = $validated['name']; - $user->mobile_number = $validated['mobile_number']; - $user->password = Hash::make($validated['password']); - $user->parentuid = $parentUser->id; - $user->acct_type = 'user'; - $user->active = true; - $user->save(); - - return Response::json([ - 'success' => true, - 'user_hashkey' => $user->hashkey, - 'message' => 'Account created. Please complete your membership application.', - ], 201); - } - - -} diff --git a/app/Http/Controllers/Market/CooperativeDocumentController.php b/app/Http/Controllers/Market/CooperativeDocumentController.php deleted file mode 100644 index cb1276d..0000000 --- a/app/Http/Controllers/Market/CooperativeDocumentController.php +++ /dev/null @@ -1,257 +0,0 @@ -input('orgHash'); - if (!$orgHash) { - return ResponseHelper::returnIncorrectDetails(); - } - - $org = Organization::where('hashkey', $orgHash)->first(); - if (!$org) { - return ResponseHelper::returnError('Organization not found', 404); - } - - // Get latest versions (where parent_hashkey IS NULL or it is the latest in its group) - // For simplicity, we'll fetch all active docs and group them by parent_hashkey or hashkey - $allDocs = CooperativeDocument::where('organization_id', $org->id) - ->where('is_active', true) - ->orderBy('version_number', 'desc') - ->get(); - - $fileHashes = $allDocs->pluck('file_hashkey')->filter()->unique()->toArray(); - $fileLists = FileList::whereIn('hashkey', $fileHashes)->with('fileContent')->get()->keyBy('hashkey'); - - $grouped = []; - foreach ($allDocs as $doc) { - $rootKey = $doc->parent_hashkey ?? $doc->hashkey; - if (!isset($grouped[$rootKey])) { - $grouped[$rootKey] = []; - } - $grouped[$rootKey][] = $doc; - } - - $data = []; - foreach ($grouped as $rootKey => $versions) { - $latest = $versions[0]; // ordered desc by version_number - $fileList = $fileLists[$latest->file_hashkey] ?? null; - if ($fileList) { - $history = []; - foreach ($versions as $v) { - $vFileList = $fileLists[$v->file_hashkey] ?? null; - if ($vFileList) { - $history[] = [ - 'hashkey' => $v->hashkey, - 'version' => $v->version_number, - 'name' => $vFileList->filename, - 'date' => $v->created_at ? $v->created_at->format('Y-m-d H:i') : null, - 'note' => $v->revision_note, - 'url' => $vFileList->resolvedUrl() - ]; - } - } - - $data[] = [ - 'hashkey' => $latest->hashkey, - 'parent_hashkey' => $latest->parent_hashkey, - 'name' => $fileList->filename, - 'type' => $latest->document_type ?? 'Document', - 'date' => $latest->created_at ? $latest->created_at->toDateString() : null, - 'size' => $this->formatBytes($fileList->fileContent->size_in_bytes ?? 0), - 'url' => $fileList->resolvedUrl(), - 'version' => $latest->version_number, - 'note' => $latest->revision_note, - 'history' => $history - ]; - } - } - - return response()->json([ - 'success' => true, - 'data' => $data - ]); - } - - public function uploadDocument(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageOrganizations)) { - return ResponseHelper::returnUnauthorized(); - } - - $orgHash = $request->input('orgHash'); - $docType = $request->input('type', 'OTHERS'); - - if (!$orgHash || !$request->hasFile('file')) { - return ResponseHelper::returnIncorrectDetails(); - } - - $org = Organization::where('hashkey', $orgHash)->first(); - if (!$org) { - return ResponseHelper::returnError('Organization not found', 404); - } - - $file = $request->file('file'); - $filename = $file->getClientFilename(); - - $fileList = FilesMainController::uploadFileList( - $file, - $filename, - $filename, - 'Cooperative Document for ' . $org->name, - ['type' => 'coop_document', 'org_id' => $org->id], - 'CooperativeDocuments', - [], - 0, - 'cooperative_document', - ); - - if (!$fileList || !isset($fileList->hashkey)) { - return ResponseHelper::returnError('File upload failed'); - } - - $doc = new CooperativeDocument([ - 'hashkey' => (string) Str::uuid(), - 'organization_id' => $org->id, - 'file_hashkey' => $fileList->hashkey, - 'document_type' => $docType, - 'created_by' => Auth::id(), - 'is_active' => true, - ]); - - if ($doc->save()) { - return response()->json([ - 'success' => true, - 'message' => 'Document uploaded successfully', - 'data' => [ - 'hashkey' => $doc->hashkey, - 'name' => $filename, - 'url' => $fileList->resolvedUrl() - ] - ]); - } - - return ResponseHelper::returnError('Failed to save document record'); - } - - public function reviseDocument(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageOrganizations)) { - return ResponseHelper::returnUnauthorized(); - } - - $parentHash = $request->input('parentHash'); - $note = $request->input('note'); - - if (!$parentHash || !$request->hasFile('file')) { - return ResponseHelper::returnIncorrectDetails(); - } - - $parentDoc = CooperativeDocument::where('hashkey', $parentHash)->first(); - if (!$parentDoc) { - return ResponseHelper::returnError('Original document not found', 404); - } - - // The real parent is either its own parent or it is the parent - $rootHash = $parentDoc->parent_hashkey ?? $parentDoc->hashkey; - - // Find highest version number - $lastVersion = CooperativeDocument::where('hashkey', $rootHash) - ->orWhere('parent_hashkey', $rootHash) - ->max('version_number'); - - $file = $request->file('file'); - $filename = $file->getClientFilename(); - - $fileList = FilesMainController::uploadFileList( - $file, - $filename, - $filename, - 'Revision of ' . $parentDoc->hashkey, - ['type' => 'coop_document_revision', 'parent_id' => $parentDoc->id], - 'CooperativeDocuments', - [], - 0, - 'cooperative_document_revision', - ); - - if (!$fileList) { - return ResponseHelper::returnError('File upload failed'); - } - - $doc = new CooperativeDocument([ - 'hashkey' => (string) Str::uuid(), - 'parent_hashkey' => $rootHash, - 'version_number' => $lastVersion + 1, - 'organization_id' => $parentDoc->organization_id, - 'file_hashkey' => $fileList->hashkey, - 'document_type' => $parentDoc->document_type, - 'revision_note' => $note, - 'created_by' => Auth::id(), - 'is_active' => true, - ]); - - if ($doc->save()) { - return response()->json([ - 'success' => true, - 'message' => 'Revision uploaded successfully', - 'data' => $doc - ]); - } - - return ResponseHelper::returnError('Failed to save revision record'); - } - - public function deleteDocument(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageOrganizations)) { - return ResponseHelper::returnUnauthorized(); - } - - $hashkey = $request->input('hashkey'); - if (!$hashkey) { - return ResponseHelper::returnIncorrectDetails(); - } - - $doc = CooperativeDocument::where('hashkey', $hashkey)->first(); - if (!$doc) { - return ResponseHelper::returnError('Document not found', 404); - } - - // If it's a version, we might want to just deactivate that version. - // If it's the root, we might want to deactivate all versions. - // For now, let's just deactivate the specific one. - $doc->is_active = false; - if ($doc->save()) { - return response()->json(['success' => true, 'message' => 'Document/Revision deleted']); - } - - return ResponseHelper::returnError('Failed to delete document'); - } - - private function formatBytes($bytes, $precision = 1) - { - $units = ['B', 'KB', 'MB', 'GB', 'TB']; - $bytes = max($bytes, 0); - $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); - $pow = min($pow, count($units) - 1); - $bytes /= (1 << (10 * $pow)); - return round($bytes, $precision) . ' ' . $units[$pow]; - } -} diff --git a/app/Http/Controllers/Market/CreditController.php b/app/Http/Controllers/Market/CreditController.php deleted file mode 100644 index 20f80f8..0000000 --- a/app/Http/Controllers/Market/CreditController.php +++ /dev/null @@ -1,269 +0,0 @@ -id) - ->where('is_active', true) - ->orderBy('created_at', 'desc') - ->limit(20) - ->get(); - - return response()->json([ - 'success' => true, - 'balance' => $user->total_balance, - 'credit' => $user->total_credit, - 'history' => $history - ]); - } - - public function topUp(Request $request) - { - // Check if Top Up is enabled globally - if (!(\App\Models\SystemSetting::getValue('top_up_enabled', true))) { - return ResponseHelper::returnError('Credit top-up is currently disabled by administrators.'); - } - - $amount = (float) $request->input('amount'); - $method = $request->input('method', 'GCASH'); - - if ($amount <= 0) { - return ResponseHelper::returnError('Amount must be greater than zero'); - } - - $user = User::find(Auth::id()); - if (!$user) return ResponseHelper::returnUnauthorized(); - - // Start Transaction - DB::beginTransaction(); - try { - // 1. Simulate Payment Success (in real life this would be a webhook/callback) - $payment = PaymentProcessor::initiatePayment($amount, $method, $user->hashkey); - - if (!$payment['success']) { - throw new \Exception('Payment initiation failed'); - } - - // 2. Update User Balance - $user->total_balance += $amount; - $user->save(); - - // 3. Record in MemberLedger - $ledger = new MemberLedger([ - 'hashkey' => Str::random(64), - 'user_id' => $user->id, - 'amount' => $amount, - 'transaction_type' => 'TOP_UP', - 'flow' => 'IN', - 'balance_after' => $user->total_balance, - 'description' => "Credit Top-up via {$method}", - 'reference_id' => $payment['transaction_id'], - 'created_by' => $user->id, - 'is_active' => true, - ]); - $ledger->save(); - - // 4. Record in GlobalTransaction (for compatibility with existing reports) - $globalTxn = new GlobalTransaction([ - 'hashkey' => Str::random(64), - 'user_id' => $user->id, - 'amount' => $amount, - 'type' => ProductTransactionType::TOP_UP, - 'status' => 'COMPLETED', - 'description' => "Credit Top-up via {$method}", - 'flow' => TransactionFlow::INCOME, - 'created_by' => $user->id, - ]); - $globalTxn->save(); - - DB::commit(); - - return response()->json([ - 'success' => true, - 'message' => 'Top-up successful', - 'balance' => $user->total_balance, - 'transaction_id' => $payment['transaction_id'] - ]); - - } catch (\Exception $e) { - DB::rollBack(); - return ResponseHelper::returnError('Top-up failed: ' . $e->getMessage()); - } - } - - public function transferCredit(Request $request) - { - $recipientHash = $request->input('recipient_hash'); - $amount = (float) $request->input('amount'); - - if ($amount <= 0) return ResponseHelper::returnError('Invalid amount'); - - $sender = User::find(Auth::id()); - if (!$sender) return ResponseHelper::returnUnauthorized(); - - if ($sender->total_balance < $amount) { - return ResponseHelper::returnError('Insufficient balance'); - } - - $recipient = User::where('hashkey', $recipientHash)->first(); - if (!$recipient) return ResponseHelper::returnError('Recipient not found'); - - if ($sender->id === $recipient->id) { - return ResponseHelper::returnError('Cannot transfer to yourself'); - } - - DB::beginTransaction(); - try { - // Deduct from sender - $sender->total_balance -= $amount; - $sender->save(); - - // Add to recipient - $recipient->total_balance += $amount; - $recipient->save(); - - $txnRef = Str::random(12); - - // Record Ledger for Sender - MemberLedger::create([ - 'hashkey' => Str::random(64), - 'user_id' => $sender->id, - 'amount' => $amount, - 'transaction_type' => 'TRANSFER_OUT', - 'flow' => 'OUT', - 'balance_after' => $sender->total_balance, - 'description' => "Credit Transfer to {$recipient->fullname}", - 'reference_id' => $txnRef, - 'created_by' => $sender->id, - ]); - - // Record Ledger for Recipient - MemberLedger::create([ - 'hashkey' => Str::random(64), - 'user_id' => $recipient->id, - 'amount' => $amount, - 'transaction_type' => 'TRANSFER_IN', - 'flow' => 'IN', - 'balance_after' => $recipient->total_balance, - 'description' => "Credit Transfer from {$sender->fullname}", - 'reference_id' => $txnRef, - 'created_by' => $sender->id, - ]); - - DB::commit(); - return response()->json(['success' => true, 'message' => 'Transfer successful']); - - } catch (\Exception $e) { - DB::rollBack(); - return ResponseHelper::returnError('Transfer failed'); - } - } - - public function searchUsers(Request $request) - { - $query = $request->input('q'); - if (empty($query)) return response()->json(['success' => true, 'data' => []]); - - $users = User::where('fullname', 'like', "%{$query}%") - ->orWhere('name', 'like', "%{$query}%") - ->orWhere('mobile_number', 'like', "%{$query}%") - ->where('id', '!=', Auth::id()) - ->limit(10) - ->get(['hashkey', 'fullname', 'name', 'mobile_number']); - - return response()->json(['success' => true, 'data' => $users]); - } - - /** - * GET the current QRPH payment code (available to all authenticated users for top-up display). - */ - public function getQrphCode(Request $request) - { - $raw = SystemSetting::getValue('qrph_payment_code', null); - $imgHashkey = SystemSetting::getValue('qrph_payment_image_hashkey', null); - - if (empty($raw)) { - return response()->json(['success' => true, 'qrph' => null, 'decoded' => null, 'image_url' => null]); - } - - $decoded = QrphDecoder::decode($raw); - $imageUrl = $imgHashkey ? "/RequestData/File/{$imgHashkey}" : null; - - return response()->json([ - 'success' => true, - 'qrph' => $raw, - 'decoded' => $decoded, - 'image_url' => $imageUrl, - ]); - } - - /** - * SET the QRPH payment code — ULTIMATE only (enforced by route middleware). - * Accepts optional image_hashkey from a prior /File/Upload/QrphPayment call. - */ - public function setQrphCode(Request $request) - { - $raw = trim($request->input('qrph_code', '')); - $imgHashkey = trim($request->input('image_hashkey', '')); - - if (empty($raw)) { - SystemSetting::setValue('qrph_payment_code', ''); - SystemSetting::setValue('qrph_payment_image_hashkey', ''); - return response()->json(['success' => true, 'message' => 'QRPH code cleared.']); - } - - $decoded = QrphDecoder::decode($raw); - - SystemSetting::setValue('qrph_payment_code', $raw); - if (!empty($imgHashkey)) { - SystemSetting::setValue('qrph_payment_image_hashkey', $imgHashkey); - } - - $imageUrl = $imgHashkey ? "/RequestData/File/{$imgHashkey}" : null; - - return response()->json([ - 'success' => true, - 'message' => 'QRPH code saved.', - 'decoded' => $decoded, - 'image_url' => $imageUrl, - ]); - } - - /** - * Decode a QRPH string without saving — for preview before saving. - */ - public function decodeQrph(Request $request) - { - $raw = trim($request->input('qrph_code', '')); - if (empty($raw)) { - return ResponseHelper::returnError('No QR string provided.'); - } - - return response()->json([ - 'success' => true, - 'decoded' => QrphDecoder::decode($raw), - ]); - } -} diff --git a/app/Http/Controllers/Market/FarmerController.php b/app/Http/Controllers/Market/FarmerController.php deleted file mode 100644 index e52d8b3..0000000 --- a/app/Http/Controllers/Market/FarmerController.php +++ /dev/null @@ -1,143 +0,0 @@ -validate([ - 'user_hash' => 'nullable|string', - 'farm_name' => 'required|string|max:255', - 'farm_location' => 'nullable|string', - 'organization_hash' => 'nullable|string', - 'main_crops' => 'nullable|array', - ]); - - // Determine the target user - if user_hash provided, use that user; otherwise use current user - if (!empty($validated['user_hash'])) { - $user = User::where('hashkey', $validated['user_hash'])->first(); - if (!$user) { - return ResponseHelper::returnError('User not found', 404); - } - } else { - $user = $currentUser; - } - - $organization = $validated['organization_hash'] ? Organization::where('hashkey', $validated['organization_hash'])->first() : null; - - // Check if user already has a farmer profile - $existingProfile = FarmerProfile::where('user_id', $user->id)->first(); - if ($existingProfile) { - return ResponseHelper::returnError('User already has a farmer profile'); - } - - $profile = new FarmerProfile([ - 'user_id' => $user->id, - 'organization_id' => $organization?->id, - 'farm_name' => $validated['farm_name'], - 'farm_location' => $validated['farm_location'], - 'main_crops' => $validated['main_crops'], - 'verification_status' => 'VERIFIED', - 'created_by' => $currentUser->id, - ]); - - if ($profile->save()) { - return ResponseHelper::returnSuccessResponse($profile, $profile->hashkey, 'Farmer profile created and pending verification'); - } - - return ResponseHelper::returnError('Failed to create farmer profile'); - } - - public function listFarmers(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewFarmers)) { - return ResponseHelper::returnUnauthorized(); - } - - $farmers = FarmerProfile::with(['user', 'organization'])->orderBy('created_at', 'desc')->get(); - return response()->json([ - 'success' => true, - 'data' => $farmers - ]); - } - - public function verifyFarmer(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::VerifyFarmer)) { - return ResponseHelper::returnUnauthorized(); - } - - $hashkey = $request->input('target'); - $status = $request->input('status'); // VERIFIED, REJECTED - - if (!$hashkey || !$status) { - return ResponseHelper::returnIncorrectDetails(); - } - - $profile = FarmerProfile::where('hashkey', $hashkey)->first(); - if (!$profile) { - return ResponseHelper::returnError('Profile not found', 404); - } - - $profile->verification_status = $status; - $profile->save(); - - return ResponseHelper::returnSuccessResponse($profile, $profile->hashkey, 'Farmer verification status updated'); - } - - public function listOrganizations() - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewOrganizations)) { - return ResponseHelper::returnUnauthorized(); - } - - $orgs = Organization::where('is_active', true)->get(); - return response()->json([ - 'success' => true, - 'data' => $orgs - ]); - } - - public function createOrganization(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::CreateOrganization)) { - return ResponseHelper::returnUnauthorized(); - } - - $validated = $request->validate([ - 'name' => 'required|string|max:255', - 'type' => 'required|string|in:COOPERATIVE,ASSOCIATION,COMPANY', - 'address' => 'nullable|string', - ]); - - $org = new Organization([ - 'name' => $validated['name'], - 'type' => $validated['type'], - 'address' => $validated['address'], - ]); - - if ($org->save()) { - return ResponseHelper::returnSuccessResponse($org, $org->hashkey, 'Organization created'); - } - - return ResponseHelper::returnError('Failed to create organization'); - } -} diff --git a/app/Http/Controllers/Market/GovernanceController.php b/app/Http/Controllers/Market/GovernanceController.php deleted file mode 100644 index b0c0e39..0000000 --- a/app/Http/Controllers/Market/GovernanceController.php +++ /dev/null @@ -1,178 +0,0 @@ -acct_type, UserActions::ViewResolutions)) { - return ResponseHelper::returnUnauthorized(); - } - - $orgHash = $request->input('org_hash'); - if (!$orgHash) { - return ResponseHelper::returnIncorrectDetails(); - } - - $org = Organization::where('hashkey', $orgHash)->first(); - if (!$org) { - return ResponseHelper::returnError('Organization not found', 404); - } - - $resolutions = CooperativeResolution::where('organization_id', $org->id) - ->where('is_active', true) - ->withCount(['votes as yes_votes' => function($query) { - $query->where('vote_cast', 'YES'); - }]) - ->withCount(['votes as no_votes' => function($query) { - $query->where('vote_cast', 'NO'); - }]) - ->withCount(['votes as abstain_votes' => function($query) { - $query->where('vote_cast', 'ABSTAIN'); - }]) - ->orderBy('created_at', 'desc') - ->get(); - - return response()->json([ - 'success' => true, - 'data' => $resolutions - ]); - } - - public function createResolution(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::CreateResolution)) { - return ResponseHelper::returnUnauthorized(); - } - - $orgHash = $request->input('org_hash'); - $title = $request->input('title'); - $description = $request->input('description'); - - if (!$orgHash || !$title) { - return ResponseHelper::returnIncorrectDetails(); - } - - $org = Organization::where('hashkey', $orgHash)->first(); - if (!$org) { - return ResponseHelper::returnError('Organization not found', 404); - } - - $resolution = new CooperativeResolution([ - 'hashkey' => Str::random(64), - 'organization_id' => $org->id, - 'title' => trim($title), - 'description' => trim($description ?? ''), - 'status' => 'PROPOSED', - 'created_by' => Auth::id(), - 'is_active' => true, - ]); - - if ($resolution->save()) { - return ResponseHelper::returnSuccessResponse($resolution, $resolution->hashkey, 'Resolution created successfully'); - } - - return ResponseHelper::returnError('Failed to create resolution'); - } - - public function castVote(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::VoteResolution)) { - return ResponseHelper::returnUnauthorized(); - } - - $resolutionHash = $request->input('resolution_hash'); - $vote = $request->input('vote'); // YES, NO, ABSTAIN - - if (!$resolutionHash || !in_array($vote, ['YES', 'NO', 'ABSTAIN'])) { - return ResponseHelper::returnIncorrectDetails(); - } - - $resolution = CooperativeResolution::where('hashkey', $resolutionHash)->first(); - if (!$resolution) { - return ResponseHelper::returnError('Resolution not found', 404); - } - - // Check if user is a member of the organization - $isMember = CooperativeMember::where('organization_id', $resolution->organization_id) - ->where('user_id', Auth::id()) - ->where('is_active', true) - ->exists(); - - if (!$isMember) { - return ResponseHelper::returnError('Only active members can vote on resolutions', 403); - } - - // Check for existing vote - $existingVote = CooperativeVote::where('resolution_id', $resolution->id) - ->where('user_id', Auth::id()) - ->first(); - - if ($existingVote) { - $existingVote->vote_cast = $vote; - $existingVote->updated_by = Auth::id(); - if ($existingVote->save()) { - return ResponseHelper::returnSuccessResponse($existingVote, $existingVote->hashkey, 'Vote updated successfully'); - } - } else { - $newVote = new CooperativeVote([ - 'hashkey' => Str::random(64), - 'resolution_id' => $resolution->id, - 'user_id' => Auth::id(), - 'vote_cast' => $vote, - 'created_by' => Auth::id(), - 'is_active' => true, - ]); - if ($newVote->save()) { - return ResponseHelper::returnSuccessResponse($newVote, $newVote->hashkey, 'Vote cast successfully'); - } - } - - return ResponseHelper::returnError('Failed to record vote'); - } - - public function updateResolutionStatus(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageOrganizations)) { - return ResponseHelper::returnUnauthorized(); - } - - $resolutionHash = $request->input('resolution_hash'); - $status = $request->input('status'); // APPROVED, RESCINDED - - if (!$resolutionHash || !in_array($status, ['APPROVED', 'RESCINDED', 'PROPOSED'])) { - return ResponseHelper::returnIncorrectDetails(); - } - - $resolution = CooperativeResolution::where('hashkey', $resolutionHash)->first(); - if (!$resolution) { - return ResponseHelper::returnError('Resolution not found', 404); - } - - $resolution->status = $status; - $resolution->updated_by = Auth::id(); - if ($status === 'APPROVED') { - $resolution->date_approved = now(); - } - - if ($resolution->save()) { - return ResponseHelper::returnSuccessResponse($resolution, $resolution->hashkey, 'Resolution status updated'); - } - - return ResponseHelper::returnError('Failed to update resolution status'); - } -} diff --git a/app/Http/Controllers/Market/PerformanceController.php b/app/Http/Controllers/Market/PerformanceController.php deleted file mode 100644 index 3201700..0000000 --- a/app/Http/Controllers/Market/PerformanceController.php +++ /dev/null @@ -1,472 +0,0 @@ -header('X-Perf-Token') ?? $request->input('token', '')); - if (!hash_equals($expected, $provided)) { - return ResponseHelper::returnError('Invalid perf token', 401); - } - return null; - } - - private function actingUser(): ?User - { - $hash = (string) env('PERF_ACTOR_HASH', ''); - if ($hash !== '') { - $u = User::where('hashkey', $hash)->first(); - if ($u) return $u; - } - return User::where('acct_type', UserTypes::ULTIMATE->value) - ->orderBy('id', 'asc') - ->first(); - } - - private function clampCount($raw): int - { - $n = (int) $raw; - if ($n < 1) $n = 1; - if ($n > self::DEFAULT_LIMIT) $n = self::DEFAULT_LIMIT; - return $n; - } - - private function ms(float $start): float - { - return round((microtime(true) - $start) * 1000, 3); - } - - public function ping(Request $request) - { - $deny = $this->authorize($request); - if ($deny) return $deny; - return Response::json([ - 'success' => true, - 'ts' => now()->toIso8601String(), - 'php' => PHP_VERSION, - ]); - } - - /** - * Core user seeder, returns ['hashes' => [...], 'ms' => float]. - */ - private function _seedUsers(User $actor, int $count, string $type, ?int $parentId, string $prefix): array - { - $parentId = $parentId ?? $actor->id; - $hashed = Hash::make('Perf12345!'); - $start = microtime(true); - $created = []; - DB::beginTransaction(); - try { - foreach (range(1, $count) as $i) { - $suffix = Str::random(10); - $u = User::create([ - 'username' => "{$prefix}_{$suffix}", - 'name' => "Perf User {$i}", - 'mobile_number' => '09' . str_pad((string) random_int(0, 999999999), 9, '0', STR_PAD_LEFT), - 'password' => $hashed, - 'acct_type' => $type, - 'parentuid' => $parentId, - 'active' => true, - ]); - $created[] = $u->hashkey; - } - DB::commit(); - } catch (\Throwable $e) { - DB::rollBack(); - throw $e; - } - return ['hashes' => $created, 'ms' => $this->ms($start)]; - } - - private function _seedStores(User $actor, int $count, string $category, ?int $ownerId, string $prefix): array - { - $start = microtime(true); - $created = []; - DB::beginTransaction(); - try { - foreach (range(1, $count) as $i) { - $storeCode = StoreController::generateStoreCode($category); - $s = Store::create([ - 'storecode' => $storeCode, - 'name' => "{$prefix} " . Str::random(8), - 'description' => "Synthetic store for perf testing #{$i}", - 'address' => 'Perf Lane ' . random_int(1, 9999), - 'category' => $category, - 'subcategory' => '', - 'owner_id' => $ownerId, - 'created_by' => $actor->id, - 'is_active' => true, - 'status' => 'active', - ]); - $created[] = $s->hashkey; - } - DB::commit(); - } catch (\Throwable $e) { - DB::rollBack(); - throw $e; - } - return ['hashes' => $created, 'ms' => $this->ms($start)]; - } - - private function _seedProducts(User $actor, int $count, ?Store $store, bool $attach, string $prefix): array - { - $start = microtime(true); - $created = []; - DB::beginTransaction(); - try { - foreach (range(1, $count) as $i) { - $price = random_int(10, 5000); - $available = random_int(10, 500); - $p = Product::create([ - 'name' => "{$prefix} " . Str::random(8), - 'description' => "Synthetic product for perf testing #{$i}", - 'price' => $price, - 'unitname' => 'pc', - 'available' => $available, - 'category' => 'Perf', - 'subcategory' => 'Synthetic', - 'created_by' => $actor->id, - 'is_active' => true, - ]); - if ($store && $attach) { - $store->products()->attach($p->id, [ - 'available' => $available, - 'price' => $price, - 'description' => $p->description, - 'is_active' => true, - ]); - } - $created[] = $p->hashkey; - } - DB::commit(); - } catch (\Throwable $e) { - DB::rollBack(); - throw $e; - } - return ['hashes' => $created, 'ms' => $this->ms($start)]; - } - - /** - * POST /api/perf/seed/users - * body: { count, type?, parent_hash?, prefix? } - */ - public function seedUsers(Request $request) - { - $deny = $this->authorize($request); - if ($deny) return $deny; - $actor = $this->actingUser(); - if (!$actor) return ResponseHelper::returnError('No actor user available', 500); - - $count = $this->clampCount($request->input('count', 10)); - $type = (string) $request->input('type', UserTypes::USER->value); - $prefix = (string) $request->input('prefix', 'perf'); - if (!UserTypes::tryFrom($type)) { - return ResponseHelper::returnError("Invalid user type '{$type}'", 422); - } - $parentId = null; - if ($p = $request->input('parent_hash')) { - $parent = User::where('hashkey', (string) $p)->first(); - if ($parent) $parentId = $parent->id; - } - try { - $r = $this->_seedUsers($actor, $count, $type, $parentId, $prefix); - } catch (\Throwable $e) { - return ResponseHelper::returnError($e->getMessage()); - } - return Response::json([ - 'success' => true, - 'count' => count($r['hashes']), - 'total_ms' => $r['ms'], - 'avg_ms' => round($r['ms'] / max(1, count($r['hashes'])), 3), - 'sample' => array_slice($r['hashes'], 0, 5), - ]); - } - - /** - * POST /api/perf/seed/stores - * body: { count, owner_hash?, category?, prefix? } - */ - public function seedStores(Request $request) - { - $deny = $this->authorize($request); - if ($deny) return $deny; - $actor = $this->actingUser(); - if (!$actor) return ResponseHelper::returnError('No actor user available', 500); - - $count = $this->clampCount($request->input('count', 10)); - $category = (string) $request->input('category', 'General'); - $prefix = (string) $request->input('prefix', 'PerfStore'); - $ownerId = null; - if ($h = $request->input('owner_hash')) { - $owner = User::where('hashkey', (string) $h)->first(); - if ($owner) $ownerId = $owner->id; - } - try { - $r = $this->_seedStores($actor, $count, $category, $ownerId, $prefix); - } catch (\Throwable $e) { - return ResponseHelper::returnError($e->getMessage()); - } - return Response::json([ - 'success' => true, - 'count' => count($r['hashes']), - 'total_ms' => $r['ms'], - 'avg_ms' => round($r['ms'] / max(1, count($r['hashes'])), 3), - 'sample' => array_slice($r['hashes'], 0, 5), - ]); - } - - /** - * POST /api/perf/seed/products - * body: { count, target_store_hash?, prefix?, attach_to_store? } - */ - public function seedProducts(Request $request) - { - $deny = $this->authorize($request); - if ($deny) return $deny; - $actor = $this->actingUser(); - if (!$actor) return ResponseHelper::returnError('No actor user available', 500); - - $count = $this->clampCount($request->input('count', 10)); - $prefix = (string) $request->input('prefix', 'PerfProduct'); - $attach = (bool) $request->input('attach_to_store', true); - $store = null; - if ($h = $request->input('target_store_hash')) { - $store = Store::where('hashkey', (string) $h)->first(); - if (!$store) return ResponseHelper::returnError('Target store not found', 404); - } - try { - $r = $this->_seedProducts($actor, $count, $store, $attach, $prefix); - } catch (\Throwable $e) { - return ResponseHelper::returnError($e->getMessage()); - } - return Response::json([ - 'success' => true, - 'count' => count($r['hashes']), - 'total_ms' => $r['ms'], - 'avg_ms' => round($r['ms'] / max(1, count($r['hashes'])), 3), - 'attached_to_store' => $store?->hashkey, - 'sample' => array_slice($r['hashes'], 0, 5), - ]); - } - - /** - * POST /api/perf/seed/batch - * body: { users?, stores?, products?, prefix?, type?, category? } - * Runs all three seeders sequentially with per-phase timings. - */ - public function seedBatch(Request $request) - { - $deny = $this->authorize($request); - if ($deny) return $deny; - $actor = $this->actingUser(); - if (!$actor) return ResponseHelper::returnError('No actor user available', 500); - - $prefix = (string) $request->input('prefix', 'perf'); - $type = (string) $request->input('type', UserTypes::USER->value); - if (!UserTypes::tryFrom($type)) { - return ResponseHelper::returnError("Invalid user type '{$type}'", 422); - } - $category = (string) $request->input('category', 'General'); - - $users = max(0, (int) $request->input('users', 0)); - $stores = max(0, (int) $request->input('stores', 0)); - $products = max(0, (int) $request->input('products', 0)); - - $phases = []; - $totalStart = microtime(true); - - try { - if ($users > 0) { - $r = $this->_seedUsers($actor, $this->clampCount($users), $type, null, $prefix); - $phases['users'] = ['count' => count($r['hashes']), 'ms' => $r['ms']]; - } - if ($stores > 0) { - $r = $this->_seedStores($actor, $this->clampCount($stores), $category, null, $prefix . 'Store'); - $phases['stores'] = ['count' => count($r['hashes']), 'ms' => $r['ms']]; - } - if ($products > 0) { - $r = $this->_seedProducts($actor, $this->clampCount($products), null, false, $prefix . 'Product'); - $phases['products'] = ['count' => count($r['hashes']), 'ms' => $r['ms']]; - } - } catch (\Throwable $e) { - return ResponseHelper::returnError($e->getMessage()); - } - - return Response::json([ - 'success' => true, - 'total_ms' => $this->ms($totalStart), - 'phases' => $phases, - ]); - } - - /** - * POST /api/perf/pos/simulate - * body: { store_hash, items?, cycles?, complete? } - * - * Runs end-to-end POS cycles entirely server-side: open session, add N - * line items, optionally complete + archive. Returns per-phase timings. - */ - public function simulatePos(Request $request) - { - $deny = $this->authorize($request); - if ($deny) return $deny; - - $actor = $this->actingUser(); - if (!$actor) return ResponseHelper::returnError('No actor user available', 500); - - $storeHash = (string) $request->input('store_hash', ''); - if ($storeHash === '') return ResponseHelper::returnError('store_hash is required', 422); - - $store = Store::where('hashkey', $storeHash)->first(); - if (!$store) return ResponseHelper::returnError('Store not found', 404); - - $items = max(1, min(200, (int) $request->input('items', 5))); - $cycles = max(1, min(100, (int) $request->input('cycles', 1))); - $complete = (bool) $request->input('complete', true); - - // Pull a pool of products attached to this store; fall back to global - // active products if the store has none yet. - $productIds = DB::table('prd_str') - ->where('store_id', $store->id) - ->where('is_active', true) - ->pluck('product_id') - ->toArray(); - - if (empty($productIds)) { - $productIds = Product::where('is_active', true) - ->limit(max(50, $items * 2)) - ->pluck('id') - ->toArray(); - } - - if (empty($productIds)) { - return ResponseHelper::returnError('No products available to simulate sales', 422); - } - - $products = Product::whereIn('id', $productIds)->get(['id', 'hashkey', 'price'])->keyBy('id'); - - $cycleResults = []; - $totalStart = microtime(true); - - for ($c = 1; $c <= $cycles; $c++) { - $cStart = microtime(true); - - // 1. Open session - $t = microtime(true); - $session = PosSession::create([ - 'access_key' => Str::random(32), - 'store_id' => $store->id, - 'customer_name' => 'Perf Customer ' . $c, - 'status' => 'active', - 'created_by' => $actor->id, - ]); - $openMs = $this->ms($t); - - // 2. Add items (raw inserts for speed, mirrors PosController::addItem) - $t = microtime(true); - $now = now(); - $rows = []; - $total = 0; - for ($i = 0; $i < $items; $i++) { - $pid = $productIds[array_rand($productIds)]; - $product = $products[$pid] ?? null; - if (!$product) continue; - $qty = random_int(1, 5); - $price = (int) $product->price; - $line = $price * $qty; - $total += $line; - $rows[] = [ - 'pos_session_id' => $session->id, - 'product_id' => $pid, - 'quantity' => $qty, - 'price_at_sale' => $price, - 'total_price' => $line, - 'hashkey' => Str::uuid()->toString() . Str::random(100), - 'created_at' => $now, - 'updated_at' => $now, - 'created_by' => $actor->id, - ]; - } - if (!empty($rows)) { - DB::table('pos_transactions')->insert($rows); - } - $addMs = $this->ms($t); - - // 3. Optionally complete the session - $completeMs = null; - if ($complete) { - $t = microtime(true); - DB::table('pos_sessions')->where('id', $session->id)->update([ - 'total_amount' => $total, - 'received_amount' => $total, - 'change_amount' => 0, - 'status' => 'completed', - 'payment_method' => 'cash', - 'updated_at' => $now, - 'updated_by' => $actor->id, - ]); - $completeMs = $this->ms($t); - } - - $cycleResults[] = [ - 'session_hash' => $session->hashkey, - 'items' => count($rows), - 'total' => $total, - 'open_ms' => $openMs, - 'add_items_ms' => $addMs, - 'complete_ms' => $completeMs, - 'cycle_ms' => $this->ms($cStart), - ]; - } - - $totalMs = $this->ms($totalStart); - $cycleMsValues = array_column($cycleResults, 'cycle_ms'); - $avg = $cycleMsValues ? array_sum($cycleMsValues) / count($cycleMsValues) : 0.0; - - return Response::json([ - 'success' => true, - 'store_hash' => $store->hashkey, - 'cycles' => $cycles, - 'items_per_cycle' => $items, - 'total_ms' => $totalMs, - 'avg_cycle_ms' => round($avg, 3), - 'min_cycle_ms' => $cycleMsValues ? min($cycleMsValues) : 0, - 'max_cycle_ms' => $cycleMsValues ? max($cycleMsValues) : 0, - 'detail' => $cycleResults, - ]); - } -} diff --git a/app/Http/Controllers/Market/PosController.php b/app/Http/Controllers/Market/PosController.php deleted file mode 100644 index 233c648..0000000 --- a/app/Http/Controllers/Market/PosController.php +++ /dev/null @@ -1,774 +0,0 @@ -validate([ - 'store_hash' => 'nullable|string', - 'customer_name' => 'nullable|string|max:255', - 'access_key' => 'nullable|string', - ]); - - $store = null; - $accessKeyObj = null; - - if ($request->input('access_key')) { - $accessKeyObj = PosAccessKey::where('access_key', $request->input('access_key')) - ->where('status', 'active') - ->first(); - - if ($accessKeyObj) { - - if ($accessKeyObj->isExpired()) { - $accessKeyObj->status = 'inactive'; - $accessKeyObj->save(); - return ResponseHelper::returnError('Access key has expired', 401); - } - - $store = $accessKeyObj->store; - } elseif (!Auth::check()) { - - return ResponseHelper::returnError('Invalid or inactive access key', 401); - } - } - - if (!$store && !empty($validated['store_hash'])) { - $store = Store::where('hashkey', $validated['store_hash'])->first(); - } - - if (!$store) { - return ResponseHelper::returnError('No store found. Please open the POS from a store page or use a valid access key.', 422); - } - - // Authorization check - if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $store)) { - return ResponseHelper::returnError('You are not authorized to start a POS session for this store.', 403); - } - - /** @var PosSession $session */ - $session = PosSession::create([ - 'access_key' => $accessKeyObj ? $accessKeyObj->access_key : Str::random(32), - 'store_id' => $store->id, - 'customer_name' => $validated['customer_name'] ?? null, - 'status' => 'active', - 'created_by' => Auth::id(), - ]); - - if ($accessKeyObj) { - $accessKeyObj->last_used_at = now(); - $accessKeyObj->save(); - } - - $this->archiveSession($session, 'Session initialized'); - $this->invalidateSessionCache($session); - - return ResponseHelper::returnSuccessResponse([ - 'hashkey' => $session->hashkey, - 'access_key' => $session->access_key, - ], $session->hashkey, 'POS Session started'); - } - - public function getSession(Request $request) - { - $hashkey = ResponseHelper::getTargetHash(); - $accessKey = $request->input('access_key'); - - if (!$hashkey && !$accessKey) { - return ResponseHelper::returnError('No key provided', 400); - } - - $session = null; - - if ($hashkey) { - $q = $this->getBaseSessionQuery()->where('hashkey', $hashkey); - $session = CacheHelper::get_cache($q); - - if (!$session) { - $session = $q->first(); - if ($session) { - CacheHelper::set_cache($q, $session); - } - } - - // If not a session hash, check if it's a store hash - if (!$session) { - $sq = Store::where('hashkey', $hashkey); - $store = CacheHelper::get_cache($sq); - if (!$store) { - $store = $sq->first(); - if ($store) { - CacheHelper::set_cache($sq, $store); - } - } - - if ($store) { - $q = $this->getBaseSessionQuery() - ->where('store_id', $store->id) - ->where('status', 'active') - ->orderBy('id', 'desc'); - - $session = CacheHelper::get_cache($q); - if (!$session) { - $session = $q->first(); - if ($session) { - CacheHelper::set_cache($q, $session); - } - } - } - } - } - - // If still no session and we have an accessKey, try that (as fallback or primary if no hashkey) - if (!$session && $accessKey) { - $q = $this->getBaseSessionQuery() - ->where('access_key', $accessKey) - ->orderBy('id', 'desc'); - - $session = CacheHelper::get_cache($q); - if (!$session) { - $session = $q->first(); - if ($session) { - CacheHelper::set_cache($q, $session); - } - } - } - - if (!$session) { - return ResponseHelper::returnError('Session not found', 404); - } - - // Authorization check - if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $session->store_id)) { - return ResponseHelper::returnError('You are not authorized to access this POS session.', 403); - } - - // Return the full session with all eager loaded relations - return ResponseHelper::returnSuccessResponse($session, $session->hashkey); - } - - private function getBaseSessionQuery() - { - return PosSession::with([ - 'transactions.product' => function ($q) { - // Only fetch minimal columns needed for the POS to reduce serialization time - $q->select(['id', 'hashkey', 'name', 'price', 'photourl', 'unitname', 'category']); - }, - 'store' - ]); - } - - public function addItem(Request $request) - { - $validated = $request->validate([ - 'session_hash' => 'required|string', - 'product_hash' => 'required|string', - 'quantity' => 'required|integer|min:1', - 'price' => 'nullable|numeric|min:0', - ]); - - $sq = PosSession::where('hashkey', $validated['session_hash']); - $session = CacheHelper::get_cache($sq); - if (!$session) { - $session = $sq->first(); - if ($session) { - CacheHelper::set_cache($sq, $session); - } - } - - $pq = Product::select(['id', 'hashkey', 'price', 'is_active', 'name'])->where('hashkey', $validated['product_hash']); - $product = CacheHelper::get_cache($pq); - if (!$product) { - $product = $pq->first(); - if ($product) { - CacheHelper::set_cache($pq, $product); - } - } - - if (!$session || !$product) { - return ResponseHelper::returnError('Session or Product not found', 404); - } - - $sessionNeedsSave = false; - if ($session->status !== 'active') { - $session->status = 'active'; - $sessionNeedsSave = true; - } - - $price = (int) $product->price; - $isActive = $product->is_active; - - if ($session->store_id) { - $psq = DB::table('prd_str') - ->where('store_id', $session->store_id) - ->where('product_id', $product->id) - ->select('price', 'is_active'); - - $storeProduct = CacheHelper::get_cache($psq); - if (!$storeProduct) { - $storeProduct = $psq->first(); - if ($storeProduct) { - CacheHelper::set_cache($psq, $storeProduct, [], 3600); // 1 hour - } - } - - if ($storeProduct) { - if (isset($storeProduct->price)) { - $price = (int) $storeProduct->price; - } - if (isset($storeProduct->is_active)) { - $isActive = (bool) $storeProduct->is_active; - } - } else { - return ResponseHelper::returnError('Product not available in this store', 403); - } - } - - if (!$isActive) { - return ResponseHelper::returnError('Product is currently inactive in this store', 403); - } - - // Use custom price if provided, otherwise use calculated store/product price - if ($request->has('price')) { - $price = (int) $validated['price']; - } - - // Update or create the transaction using raw DB for max speed - $existingTx = DB::table('pos_transactions') - ->where('pos_session_id', $session->id) - ->where('product_id', $product->id) - ->first(); - - if ($existingTx) { - DB::table('pos_transactions')->where('id', $existingTx->id)->update([ - 'quantity' => $validated['quantity'], - 'price_at_sale' => $price, - 'total_price' => $price * $validated['quantity'], - 'updated_at' => now(), - ]); - } else { - DB::table('pos_transactions')->insert([ - 'pos_session_id' => $session->id, - 'product_id' => $product->id, - 'quantity' => $validated['quantity'], - 'price_at_sale' => $price, - 'total_price' => $price * $validated['quantity'], - 'hashkey' => \Hyperf\Stringable\Str::uuid()->toString() . \Hyperf\Stringable\Str::random(100), - 'created_at' => now(), - 'updated_at' => now(), - 'created_by' => Auth::id() ?? null, - ]); - } - - // Load specific columns to be fast, just like in getSession to reduce payload and memory - $session->load([ - 'transactions.product' => function ($q) { - $q->select(['id', 'hashkey', 'name', 'price', 'photourl', 'unitname', 'category']); - }, - 'store' - ]); - $t_load = microtime(true); - - // Update session totals in memory - $total = $session->transactions->where('is_void', false)->sum('total_price'); - - $updateData = []; - if ($session->total_amount !== (int) $total) { - $session->total_amount = (int) $total; - $updateData['total_amount'] = (int) $total; - } - if ($sessionNeedsSave) { - $updateData['status'] = $session->status; - } - - // Use raw DB update to skip ModelSavingListener overhead while making sure we still record who updated it - if (!empty($updateData)) { - $updateData['updated_at'] = now(); - $updateData['updated_by'] = Auth::id(); - DB::table('pos_sessions')->where('id', $session->id)->update($updateData); - } - $t_db = microtime(true); - - // Invalidate all possible session cache keys - $this->invalidateSessionCache($session); - - // Archive the session using already loaded transaction data (deferred to background coroutine) - $this->archiveSession($session, 'Item added/updated: ' . $product->name, $session->transactions); - - return ResponseHelper::returnSuccessResponse($session, $session->hashkey, 'Item added to session'); - } - - public function removeItem(Request $request) - { - $validated = $request->validate([ - 'session_hash' => 'required|string', - 'transaction_id' => 'required|integer', - ]); - - $session = PosSession::where('hashkey', $validated['session_hash'])->first(); - if (!$session) { - return ResponseHelper::returnError('Session not found', 404); - } - - $transaction = PosTransaction::where('id', $validated['transaction_id']) - ->where('pos_session_id', $session->id) - ->first(); - - if ($transaction) { - $transaction->delete(); - - // Re-calculate and archive efficiently - // Load relations ONCE with only necessary columns - $session->load([ - 'transactions.product' => function ($q) { - $q->select(['id', 'hashkey', 'name', 'price', 'photourl', 'unitname', 'category']); - }, - 'store' - ]); - - $total = $session->transactions->where('is_void', false)->sum('total_price'); - $session->total_amount = (int) $total; - DB::table('pos_sessions')->where('id', $session->id)->update([ - 'total_amount' => (int) $total, - 'updated_at' => now(), - 'updated_by' => Auth::id(), - ]); - - // Invalidate all possible session cache keys - $this->invalidateSessionCache($session); - - $this->archiveSession($session, 'Item removed', $session->transactions); - } else { - $session->load([ - 'transactions.product' => function ($q) { - $q->select(['id', 'hashkey', 'name', 'price', 'photourl', 'unitname', 'category']); - }, - 'store' - ]); - } - - return ResponseHelper::returnSuccessResponse($session, $session->hashkey, 'Item removed from session'); - } - - public function completeSession(Request $request) - { - $validated = $request->validate([ - 'session_hash' => 'required|string', - 'received_amount' => 'required|integer|min:0', - 'payment_method' => 'required|string', - 'payment_field' => 'nullable|string', - 'customer_name' => 'nullable|string|max:255', - ]); - - $session = PosSession::where('hashkey', $validated['session_hash'])->first(); - if (!$session) { - return ResponseHelper::returnError('Session not found', 404); - } - - $session->received_amount = $validated['received_amount']; - $session->change_amount = $validated['received_amount'] - $session->total_amount; - $session->payment_method = $validated['payment_method']; - $session->payment_details = ['payment_field' => $validated['payment_field']]; - if (!empty($validated['customer_name'])) { - $session->customer_name = $validated['customer_name']; - } - $session->status = 'completed'; - $session->save(); - - // Invalidate cache - $this->invalidateSessionCache($session); - - if (!empty($validated['customer_name'])) { - $customerName = trim($validated['customer_name']); - $customer = Customer::where('name', $customerName) - ->where(function ($q) use ($session) { - $q->where('store_id', $session->store_id) - ->orWhereNull('store_id'); - }) - ->first(); - - if (!$customer) { - Customer::create([ - 'name' => $customerName, - 'store_id' => $session->store_id, - 'created_by' => Auth::id(), - ]); - } else { - $customer->updated_at = now(); - $customer->save(); - } - } - - $this->archiveSession($session, 'Session completed'); - - return ResponseHelper::returnSuccessResponse($session, $session->hashkey, 'Transaction completed'); - } - - public function syncOffline(Request $request) - { - $validated = $request->validate([ - 'transactions' => 'required|array', - 'transactions.*.store_hash' => 'required|string', - 'transactions.*.customer_name' => 'nullable|string', - 'transactions.*.items' => 'required|array', - 'transactions.*.total' => 'required|numeric', - 'transactions.*.received' => 'required|numeric', - 'transactions.*.method' => 'required|string', - 'transactions.*.timestamp' => 'required|string', - 'transactions.*.local_id' => 'nullable|integer', - ]); - - $syncedCount = 0; - $syncedIds = []; - $errors = []; - $affectedStoreIds = []; - - foreach ($validated['transactions'] as $txn) { - try { - DB::beginTransaction(); - - $store = Store::where('hashkey', $txn['store_hash'])->first(); - if (!$store) { - throw new \Exception('Store not found for hash: ' . $txn['store_hash']); - } - - // Convert ISO 8601 timestamp to MySQL datetime format - $offlineTimestamp = date('Y-m-d H:i:s', strtotime($txn['timestamp'])); - - // Create the session - $session = new PosSession([ - 'store_id' => $store->id, - 'customer_name' => $txn['customer_name'] ?? null, - 'total_amount' => (int) $txn['total'], - 'received_amount' => (int) $txn['received'], - 'change_amount' => (int) ($txn['received'] - $txn['total']), - 'payment_method' => $txn['method'], - 'status' => 'completed', - 'created_by' => Auth::id(), - 'access_key' => 'synced-' . Str::random(32), - 'hashkey' => Str::random(32) . '-' . Str::random(100), - ]); - - // Manually set timestamps to preserve offline time - $session->created_at = $offlineTimestamp; - $session->updated_at = $offlineTimestamp; - $session->save(); - - // Add Items - foreach ($txn['items'] as $item) { - $product = Product::where('hashkey', $item['product_hashkey'])->first(); - if (!$product) continue; - - DB::table('pos_transactions')->insert([ - 'pos_session_id' => $session->id, - 'product_id' => $product->id, - 'quantity' => $item['quantity'], - 'price_at_sale' => (int) $item['price_at_sale'], - 'total_price' => (int) ($item['price_at_sale'] * $item['quantity']), - 'hashkey' => Str::uuid()->toString() . Str::random(100), - 'created_at' => $offlineTimestamp, - 'updated_at' => now(), - 'created_by' => Auth::id(), - ]); - } - - // Handle Customer - if (!empty($txn['customer_name'])) { - $customerName = trim($txn['customer_name']); - $customer = Customer::where('name', $customerName) - ->where(function ($q) use ($store) { - $q->where('store_id', $store->id) - ->orWhereNull('store_id'); - }) - ->first(); - - if (!$customer) { - Customer::create([ - 'name' => $customerName, - 'store_id' => $store->id, - 'created_by' => Auth::id(), - ]); - } - } - - $this->archiveSession($session, 'Offline synced transaction'); - $this->invalidateSessionCache($session); - - DB::commit(); - $syncedCount++; - - if (isset($txn['local_id'])) { - $syncedIds[] = $txn['local_id']; - } - - if (!in_array($store->id, $affectedStoreIds)) { - $affectedStoreIds[] = $store->id; - } - } catch (\Exception $e) { - DB::rollBack(); - $errors[] = $e->getMessage(); - } - } - - return ResponseHelper::returnSuccessResponse([ - 'synced_count' => $syncedCount, - 'synced_ids' => $syncedIds, - 'errors' => $errors - ], 'sync_offline', "Synced $syncedCount transactions"); - } - - public function voidSession(Request $request) - { - $validated = $request->validate([ - 'session_hash' => 'required|string', - 'remarks' => 'nullable|string', - ]); - - $session = PosSession::where('hashkey', $validated['session_hash'])->first(); - if (!$session) { - return ResponseHelper::returnError('Session not found', 404); - } - - if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $session->store_id)) { - return ResponseHelper::returnError('You are not authorized to void this POS session.', 403); - } - - $session->status = 'voided'; - $session->is_void = true; - $session->save(); - - // Invalidate cache - $this->invalidateSessionCache($session); - - $this->archiveSession($session, 'Session voided: ' . ($validated['remarks'] ?? 'No remarks')); - - return ResponseHelper::returnSuccessResponse($session, $session->hashkey, 'Transaction voided'); - } - - public function getPosSessions(Request $request) - { - $validated = $request->validate([ - 'store_hash' => 'required|string', - 'page' => 'nullable|integer|min:1', - 'per_page' => 'nullable|integer|min:1|max:100', - ]); - - $store = Store::where('hashkey', $validated['store_hash'])->first(); - if (!$store) { - return ResponseHelper::returnError('Store not found', 404); - } - - // Authorization check - if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $store)) { - return ResponseHelper::returnError('You are not authorized to view sessions for this store.', 403); - } - - $page = (int) ($validated['page'] ?? 1); - $perPage = (int) ($validated['per_page'] ?? 10); - $offset = ($page - 1) * $perPage; - - $query = PosSession::with(['transactions.product']) - ->where('store_id', $store->id) - ->where('status', '!=', 'active') - ->orderBy('id', 'desc'); - - $totalCount = $query->count(); - $sessions = $query->limit($perPage)->offset($offset)->get(); - - // Map sessions to include item count and simplify if needed - $sessions = $sessions->map(function ($session) { - return [ - 'hashkey' => $session->hashkey, - 'status' => $session->status, - 'total_amount' => $session->total_amount, - 'customer_name' => $session->customer_name, - 'payment_method' => $session->payment_method, - 'items_count' => $session->transactions->count(), - 'created_at' => $session->created_at, - 'transactions' => $session->transactions, - ]; - }); - - return ResponseHelper::returnSuccessResponse([ - 'sessions' => $sessions, - 'total_count' => $totalCount, - 'page' => $page, - 'per_page' => $perPage, - ], $store->hashkey, 'POS sessions fetched'); - } - - public function getTodayStats(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewPosReports)) { - return ResponseHelper::returnUnauthorized(); - } - - $date = now()->format('Y-m-d'); - $query = PosSession::where('status', 'completed') - ->whereDate('created_at', $date); - - $storeName = null; - $storePhoto = null; - // If store_hash is provided, filter by store - if ($request->input('store_hash')) { - $store = Store::where('hashkey', $request->input('store_hash'))->first(); - if ($store) { - // Authorization check - if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $store)) { - return ResponseHelper::returnError('You are not authorized to view reports for this store.', 403); - } - $query->where('store_id', $store->id); - $storeName = $store->name; - $storePhoto = $store->photourl; - } - } - - $stats = CacheHelper::get_cache($query); - if ($stats) { - return ResponseHelper::returnSuccessResponse($stats, 'today_stats'); - } - - $stats = [ - 'count' => (int) $query->count(), - 'total' => (int) $query->sum('total_amount'), - 'store_name' => $storeName, - 'store_photo' => $storePhoto, - ]; - - CacheHelper::set_cache($query, $stats, [], 300); // 5 minutes - - return ResponseHelper::returnSuccessResponse($stats, 'today_stats'); - } - - public function getCustomers(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewCustomers)) { - return ResponseHelper::returnUnauthorized(); - } - - $queryText = $request->input('query'); - $storeHash = $request->input('store_hash'); - - $customerQuery = Customer::where('is_active', true); - - if ($storeHash) { - $store = Store::where('hashkey', $storeHash)->first(); - if ($store) { - // Authorization check - if (!UserPermissions::isUserAllowedAccessToStore(Auth::user(), $store)) { - return ResponseHelper::returnError('You are not authorized to view customers for this store.', 403); - } - $customerQuery->where(function ($q) use ($store) { - $q->where('store_id', $store->id) - ->orWhereNull('store_id'); - }); - } - } - - if ($queryText) { - $customerQuery->where('name', 'like', '%' . $queryText . '%'); - } - - $finalQuery = $customerQuery->orderBy('name', 'asc')->limit(10); - $customers = CacheHelper::get_cache($finalQuery); - - if (!$customers) { - $customers = $finalQuery->get(); - CacheHelper::set_cache($finalQuery, $customers, [], 3600); // 1 hour - } - - return ResponseHelper::returnSuccessResponse($customers, 'customer_suggestions'); - } - - private function updateSessionTotals(PosSession $session) - { - $total = $session->transactions()->where('is_void', false)->sum('total_price'); - $session->total_amount = (int) $total; - $session->save(); - } - - private function archiveSession(PosSession $session, string $remarks = '', $transactions = null) - { - // Serialize all data NOW before spawning coroutine to avoid context issues - $sessionData = $session->toArray(); - $sessionId = $session->id; - $sessionHashkey = $session->hashkey; - $userId = Auth::id(); - - if ($transactions !== null) { - $transactionsData = $transactions->toArray(); - } else { - $transactionsData = $session->transactions()->with('product')->get()->toArray(); - } - - // Defer the archive INSERT to a background coroutine so it doesn't block the response - Coroutine::create(function () use ($sessionId, $sessionHashkey, $sessionData, $transactionsData, $userId, $remarks) { - try { - PosSessionArchive::create([ - 'pos_session_id' => $sessionId, - 'hashkey' => $sessionHashkey, - 'session_snapshot' => $sessionData, - 'transactions_snapshot' => $transactionsData, - 'created_by' => $userId, - 'remarks' => $remarks, - ]); - } catch (\Throwable $e) { - // Silently fail — archive is non-critical audit data - } - }); - } - - private function invalidateSessionCache(PosSession $session) - { - // 1. Invalidate by hashkey (with relations) - CacheHelper::erase_cache($this->getBaseSessionQuery()->where('hashkey', $session->hashkey)); - - // 1b. Invalidate simple hashkey lookup (used in addItem) - CacheHelper::erase_cache(PosSession::where('hashkey', $session->hashkey)); - - // 2. Invalidate by store_id (last active session) - if ($session->store_id) { - CacheHelper::erase_cache($this->getBaseSessionQuery() - ->where('store_id', $session->store_id) - ->where('status', 'active') - ->orderBy('id', 'desc')); - } - - // 3. Invalidate by access_key - if ($session->access_key) { - CacheHelper::erase_cache($this->getBaseSessionQuery() - ->where('access_key', $session->access_key) - ->orderBy('id', 'desc')); - } - // 4. Invalidate today stats cache for the store - if ($session->store_id) { - $date = now()->format('Y-m-d'); - $statsQuery = PosSession::where('status', 'completed') - ->whereDate('created_at', $date) - ->where('store_id', $session->store_id); - CacheHelper::erase_cache($statsQuery); - } - } -} diff --git a/app/Http/Controllers/Market/ProductController.php b/app/Http/Controllers/Market/ProductController.php deleted file mode 100644 index ff3a16a..0000000 --- a/app/Http/Controllers/Market/ProductController.php +++ /dev/null @@ -1,1140 +0,0 @@ -acct_type, [ - // UserTypes::ULTIMATE, - // UserTypes::OPERATOR, - // UserTypes::SUPER_OPERATOR, - // ], true); - - // if (!$user || !$IsAdmin) { - // return response()->json(['success' => false, 'message' => 'Unauthorized '], 403); - // } - - $canCreateGlobal = ProductPermissions::isActionAllowed(UserActions::CreateProductGlobal); - $canCreateForOwnStore = ProductPermissions::isActionAllowed(UserActions::CreateProductForOwnStore); - - if (!$canCreateGlobal && !$canCreateForOwnStore) { - return ResponseHelper::returnUnauthorized(); - } - - // Validate request input - $validated = $request->validate([ - 'NewProductName' => 'required|string|max:255', - 'NewProductAvailable' => 'required|numeric', - 'NewProductBarcode' => 'nullable|string|max:255', - 'NewProductCategory' => 'nullable|string|max:255', - 'NewProductSubCategory' => 'nullable|string|max:255', - 'NewProductDescription' => 'required|string', - 'NewProductPrice' => 'required|numeric|min:0', - 'NewProductUnitName' => 'required|string|max:100', - 'files' => 'nullable|array', - 'photourl' => 'nullable|array', - 'TargetStore' => 'nullable|string', - ]); - - $targetStoreHash = $validated['TargetStore'] ?? null; - $targetStore = null; - - if (!$canCreateGlobal && !$targetStoreHash) { - return ResponseHelper::returnUnauthorized('A target store is required to create a product.'); - } - - if ($targetStoreHash) { - $targetStore = Store::where('hashkey', $targetStoreHash)->first(); - if (!$targetStore) { - return response()->json(['success' => false, 'message' => 'Target store not found'], 404); - } - - // Permission check for target store - /** @var User $user */ - $user = Auth::user(); - $acctType = $user->acct_type instanceof UserTypes ? $user->acct_type : UserTypes::tryFrom($user->acct_type); - $isBig3 = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR]); - - $isAllowedAccess = false; - if ($isBig3) { - $isAllowedAccess = true; - } else { - // Owner, Primary Manager, or New Managers list - if ($targetStore->owner_id === $user->id || $targetStore->manager_id === $user->id) { - $isAllowedAccess = true; - } elseif ($targetStore->managers()->where('user_id', $user->id)->exists()) { - $isAllowedAccess = true; - } else { - // Ancestor check - if (UserPermissions::isDescendantOfCurrentUser($targetStore->owner_id)) { - $isAllowedAccess = true; - } else { - $managerIds = $targetStore->managerUsers()->pluck('users.id')->toArray(); - foreach ($managerIds as $managerId) { - if (UserPermissions::isDescendantOfCurrentUser($managerId)) { - $isAllowedAccess = true; - break; - } - } - } - } - } - - if (!$isAllowedAccess) { - return ResponseHelper::returnUnauthorized(); - } - } - - // Create new product - $product = Product::create([ - 'name' => $validated['NewProductName'], - 'description' => $validated['NewProductDescription'], - 'price' => $validated['NewProductPrice'], - 'unitname' => $validated['NewProductUnitName'], - 'available' => $validated['NewProductAvailable'], - 'barcode' => $validated['NewProductBarcode'] ?? null, - 'category' => $validated['NewProductCategory'] ?? null, - 'subcategory' => $validated['NewProductSubCategory'] ?? null, - 'photourl' => $validated['photourl'] ?? $validated['files'] ?? null, - 'created_by' => $user->id, - ]); - - if ($product) { - if ($targetStore) { - /** @var Store $targetStore */ - /** @var Product $product */ - StoreController::attachProducttoStore($targetStore, $product, (int) $product->available, (int) $product->price); - } - - return response()->json([ - 'success' => true, - 'data' => [ - 'hashkey' => $product->hashkey, - ], - ]); - } - - return response()->json(['success' => false, 'message' => 'Failed to create product'], 500); - } - - - private static function searchProductTypes($array, $searchTerm) - { - $results = []; - foreach ($array as $key => $value) { - if (stripos($key, $searchTerm) !== false) { - $results[$key] = $value; - } - if (is_array($value)) { - $recursiveResults = self::searchProductTypes($value, $searchTerm); - - foreach ($recursiveResults as $rKey => $rValue) { - if (array_key_exists($rKey, $results)) { - if (is_array($results[$rKey]) && is_array($rValue)) { - $results[$rKey] = array_merge_recursive($results[$rKey], $rValue); - } else { - $results[$rKey] = array($results[$rKey], $rValue); - } - } else { - $results[$rKey] = $rValue; - } - } - } - } - return $results; - } - - public function getCategories(Request|false $request = false) - { - $nonRequest = $request == false; - - - - if (!$nonRequest) { - $category = $request->input('category'); - } else { - $category = false; - } - - $categories = array_keys(Config::get('market.productsCategorySubcategory')); - - if (!$nonRequest) { - return response()->json([ - 'success' => true, - 'categories' => $categories, - ]); - } - - return $categories; - } - - public function getSubcategories(Request|false $request = false, string|false $category = false) - { - - $request = $request ?: request(); - $category = $request ? $request->input('category') : $category; - - $categoryList = Config::get('market.productsCategorySubcategory', []); - - if (!$category || !array_key_exists($category, $categoryList)) { - if ($request) { - return response()->json([], 400); - } - return []; - } - - $subcategories = $categoryList[$category] ?? []; - - if ($request) { - return response()->json($subcategories); - } - - return $subcategories; - } - - - public static function viewProductDetails(string|false $target = false, $withStores = false) - { - $hashfromRequest = request()->input('target'); - $datafromRequest = request()->input('data'); - $targetStoreHash = null; - $hashkey = $hashfromRequest ?? $target; - $storeProductAvailable = null; - $storeProductPrice = null; - $storeProductIsActive = null; - $storeProductRemarks = null; - $storeProductDescription = null; - $storeProductSold = null; - $storeProductReviews = null; - $isFromStore = false; - $storeProductQrCode = null; - - - - - - - if ($datafromRequest && is_array($datafromRequest) && isset($datafromRequest['store_hash'])) { - $targetStoreHash = $datafromRequest['store_hash']; - try { - $targetStore = Store::where('hashkey', $targetStoreHash)->firstOrFail(); - } catch (\Throwable $th) { - return ResponseHelper::returnIncorrectDetails(); - } - } else { - $targetStore = null; - } - - - - - if (!$hashkey || is_numeric($hashkey)) { - return ResponseHelper::returnFalseOrNull($hashfromRequest); - } - - - - $product = Product::where('hashkey', $hashkey) - ->first(); - - if (!$product) { - return ResponseHelper::returnFalseOrNull($hashfromRequest); - } - - - - - if ($targetStore) { - $storeProduct = $targetStore->products() - ->where('prd_items.id', $product->id) - ->first(); - - if (!$storeProduct) { - return ResponseHelper::returnFalseOrNull($hashfromRequest); - } - - $isFromStore = true; - - $storeProductAvailable = $storeProduct->pivot->available; - $storeProductPrice = $storeProduct->pivot->price; - $storeProductIsActive = $storeProduct->pivot->is_active; - $storeProductRemarks = $storeProduct->pivot->remarks; - $storeProductDescription = $storeProduct->pivot->description; - $storeProductSold = $storeProduct->pivot->sold; - $storeSoldToday = ProductTransaction::where('product_id', $product->id) - ->where('store_id', $targetStore->id) - ->whereDate('created_at', today()) - ->sum('quantity'); - $storeProductReviews = $storeProduct->pivot->reviews; - - // Store specific QR code: hashkey of product + store - $storeProductQrCode = md5($product->hashkey . $targetStore->hashkey); - } - - $soldToday = ProductTransaction::where('product_id', $product->id) - ->whereDate('created_at', today()) - ->sum('quantity'); - - - // $productActive = $product->status === 'active'; - // $productStockAvailable = $product->available > 0; - // $storeActive = $store && $store->status === 'active'; - - // if (!$store || !$productActive || !$productStockAvailable || !$storeActive) { - // if ($hashfromRequest) { - // return response()->json(['success' => false]); - // } else { - // return null; - // } - // } - - - - $data = [ - 'hashkey' => $product->hashkey, - 'name' => $product->name, - 'store_description' => $storeProductDescription, - 'description' => $product->description, - 'category' => $product->category, - 'subcategory' => $product->subcategory, - 'unitname' => $product->unitname, - 'is_from_store' => $isFromStore, - 'price' => $product->price, - 'store_price' => $storeProductPrice, - 'store_available' => $storeProductAvailable, - 'available' => $product->available, - - 'store_sold' => $storeProductSold, - 'sold' => $product->sold, - 'sold_today' => (int) $soldToday, - 'store_sold_today' => isset($storeSoldToday) ? (int) $storeSoldToday : null, - - 'store_reviews' => $storeProductReviews, - 'reviews' => $product->reviews, - - 'store_remarks' => $storeProductRemarks, - 'remarks' => $product->remarks, - 'photourl' => $product->photourl, - 'barcode' => $product->barcode, - 'pos_qrcode' => $storeProductQrCode ?? $product->barcode ?? $product->hashkey, - 'is_active' => $storeProductIsActive ?? $product->is_active, - ]; - - - if ($withStores) { - $data['store_options'] = Store::select(['hashkey', 'name']) - ->get() - ->map(function ($store) { - return [$store->hashkey, $store->name]; - }); - } - - - - - if ($data['photourl']) { - $data['photourlDropzone'] = PhotoURL::photoURLArraytoDropzoneData($data['photourl']); - } - - if ($hashfromRequest) { - return ResponseHelper::returnSuccessResponse($data, $data['hashkey']); - } else { - return $data; - } - } - public static function viewProductDetailsByStoreEdit(string|false $target = false, $withStores = false) - { - // This method is intended to be called when we specifically want to view a product - // in the context of a store (e.g., for store-specific editing). - // It ensures store_hash is present or identifies it from context. - - $req = request(); - $target = $target ?: $req->input('target'); - $storeHash = $req->input('store_hash') ?? ($req->input('data')['store_hash'] ?? null); - - if (!$storeHash) { - // Fallback: If no store hash is provided, maybe we shouldn't allow store-specific view? - // But for now, let's let viewProductDetails handle the fallback. - } - - return self::viewProductDetails($target, $withStores); - } - - - - public static function editProductAdmin(Request|false $request = false, string|false $target = false) - { - /** @var User $user */ - $user = Auth::user(); - $acctType = $user->acct_type instanceof UserTypes ? $user->acct_type : UserTypes::tryFrom($user->acct_type); - $isBig3 = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR]); - - $req = $request ?: request(); - $hash = $target ?: ($req?->input('target') ?? null); - $data = $req?->input('data') ?? []; - $storeHash = $data['store_hash'] ?? null; - - // Global edit requires ModifyAllProducts. Per-store edits are gated - // below by per-store ownership instead. - $canModifyGlobal = ProductPermissions::isActionAllowed(UserActions::ModifyAllProducts); - $canModifyOwnStoreProduct = ProductPermissions::isActionAllowed(UserActions::ModifyOwnProduct) - || ProductPermissions::isActionAllowed(UserActions::AddProducttoOwnStore); - - if (!$canModifyGlobal && !($storeHash && $canModifyOwnStoreProduct)) { - return $target ? false : ResponseHelper::returnUnauthorized(); - } - - if (!$hash) { - return $target ? false : response()->json(['success' => false, 'message' => 'No target specified'], 400); - } - - $product = Product::where('hashkey', $hash)->first(); - - if (!$product) { - return $target ? false : response()->json(['success' => false, 'message' => 'Product not found'], 404); - } - - $isStoreUpdate = (bool)$storeHash; - - $validated = $req->validate([ - 'EditProductName' => $isStoreUpdate ? 'nullable|string|max:255' : 'required|string|max:255', - 'EditProductAvailable' => 'nullable|numeric', - 'EditProductBarcode' => 'nullable|string|max:255', - 'EditProductCategory' => 'nullable|string|max:255', - 'EditProductSubCategory' => 'nullable|string|max:255', - 'EditProductDescription' => 'required|string', - 'EditProductPrice' => 'required|numeric|min:0', - 'EditProductUnitName' => $isStoreUpdate ? 'nullable|string|max:100' : 'required|string|max:100', - 'files' => 'nullable|array', - 'photourl' => 'nullable|array', - 'status' => 'nullable|boolean', - ]); - - if ($storeHash) { - $store = Store::where('hashkey', $storeHash)->first(); - if ($store) { - $isAllowedAccess = false; - if ($isBig3) { - $isAllowedAccess = true; - } else { - if ($store->owner_id === $user->id || $store->manager_id === $user->id || $store->managers()->where('user_id', $user->id)->exists()) { - $isAllowedAccess = true; - } else { - if (UserPermissions::isDescendantOfCurrentUser($store->owner_id)) { - $isAllowedAccess = true; - } else { - $managerIds = $store->managerUsers()->pluck('users.id')->toArray(); - foreach ($managerIds as $managerId) { - if (UserPermissions::isDescendantOfCurrentUser($managerId)) { - $isAllowedAccess = true; - break; - } - } - } - } - } - - if ($isAllowedAccess) { - $product->stores()->syncWithoutDetaching([ - $store->id => [ - 'available' => $validated['EditProductAvailable'] ?? 0, - 'price' => $validated['EditProductPrice'] ?? 0, - 'is_active' => $validated['status'] ?? true, - 'description' => $validated['EditProductDescription'], - 'remarks' => $validated['EditProductDescription'], // Keep for compatibility - ] - ]); - - return $target ? true : response()->json([ - 'success' => true, - 'message' => 'Store-specific product data updated successfully', - 'hashkey' => $product->hashkey, - ]); - } - } - } - - // PERMISSION CHECK: Restrict global product editing exclusively to the "Big 3" roles. - if (!$isBig3) { - return $target ? false : ResponseHelper::returnError('Global product editing is restricted to administrators.', 403); - } - - $product->update([ - 'name' => $validated['EditProductName'], - 'description' => $validated['EditProductDescription'], - 'price' => $validated['EditProductPrice'], - 'unitname' => $validated['EditProductUnitName'], - 'available' => $validated['EditProductAvailable'] ?? $product->available, - 'barcode' => $validated['EditProductBarcode'] ?? null, - 'category' => $validated['EditProductCategory'] ?? null, - 'subcategory' => $validated['EditProductSubCategory'] ?? null, - 'photourl' => $validated['photourl'] ?? $validated['files'] ?? $product->photourl, - 'is_active' => $validated['status'] ?? $product->is_active, - ]); - - return $target ? true : response()->json([ - 'success' => true, - 'message' => $product->wasChanged() ? 'Product updated successfully' : 'No changes were needed', - 'hashkey' => $product->hashkey, - ]); - } - - - public static function editProductAdminByStore(Request|false $request = false, string|false $target = false) - { - $req = $request ?: request(); - - $hash = $target ?: ($req?->input('target') ?? null); - - if (!$hash) { - return $target ? false : response()->json(['success' => false, 'message' => 'No target specified'], 400); - } - - $product = Product::where('hashkey', $hash)->first(); - - if (!$product) { - return $target ? false : response()->json(['success' => false, 'message' => 'Product not found'], 404); - } - - // return response()->json(['success' => false, 'message' => 'Product not found'], 404); - - - - $validated = $req->validate([ - 'EditProductName' => 'required|string|max:255', - // 'EditProductAvailable' => 'required|numeric', - 'EditProductBarcode' => 'nullable|string|max:255', - 'EditProductCategory' => 'nullable|string|max:255', - 'EditProductSubCategory' => 'nullable|string|max:255', - 'EditProductDescription' => 'required|string', - 'EditProductPrice' => 'required|numeric|min:0', - 'EditProductUnitName' => 'required|string|max:100', - 'files' => 'nullable|array', - // 'status' => 'nullable|boolean', - ]); - - //TODO Check first if store_id is owned by current user or is descendant - //TODO Then update the pivot table instead of the main product table - - - $product->update([ - // 'name' => $validated['EditProductName'], - 'description' => $validated['EditProductDescription'], - 'price' => $validated['EditProductPrice'], - - // 'available' => $validated['EditProductAvailable'], - // 'barcode' => $validated['EditProductBarcode'] ?? null, - // 'is_active' => $validated['status'] ?? $product->is_active, // uncomment if you include this field - ]); - - if ($product->wasChanged()) { - return $target ? true : response()->json([ - 'success' => true, - 'message' => 'Product updated successfully', - 'hashkey' => $product->hashkey, - ]); - } - - return $target ? false : response()->json([ - 'success' => false, - 'message' => 'No changes were made', - ], 500); - } - - public function listProductsData(Request $request) - { - // $wholesaleOnly = $request->boolean('wholesale_only', false); - - // Fetch active and available products - - $user = Auth::user(); - $UltOpsSupOps = $user && in_array($user->acct_type, [ - UserTypes::ULTIMATE, - UserTypes::OPERATOR, - UserTypes::SUPER_OPERATOR, - ], true); - - $storeHash = $request->input('store_hash'); - $accessKey = $request->input('access_key'); - $targetStore = null; - - $sessionHash = $request->input('session_hash'); - - if ($accessKey) { - $keyObj = \App\Models\Market\PosAccessKey::where('access_key', $accessKey)->first(); - if ($keyObj) { - $targetStore = $keyObj->store; - } - } - - if (!$targetStore && $sessionHash) { - $sessionObj = \App\Models\Market\PosSession::where('hashkey', $sessionHash)->first(); - if ($sessionObj) { - $targetStore = $sessionObj->store; - } - } - - if (!$targetStore && $storeHash) { - $targetStore = Store::where('hashkey', $storeHash)->first(); - } - - if ($targetStore) { - $products = $targetStore->products() - ->where('prd_str.is_active', true) - ->get(); - } elseif ($UltOpsSupOps) { - $products = Product::select(['description', 'name', 'price', 'unitname', 'photourl', 'hashkey', 'barcode'])->get(); - } else { - $products = Product::where('is_active', true) - ->get(); - } - - $products = $products->map(function ($product) use ($targetStore) { - $photo = $product->photourl; - - $price = $product->price; - $qrcode = $product->barcode ?? $product->hashkey; - $available = $product->available; - - if ($targetStore && isset($product->pivot)) { - $price = $product->pivot->price ?? $product->price; - $qrcode = md5($product->hashkey . $targetStore->hashkey); - $available = $product->pivot->available ?? $product->available; - } - - return [ - 'description' => $product->description, - 'name' => $product->name, - 'price' => $price, - 'unit' => $product->unitname, - 'photo' => $photo, - 'hashkey' => $product->hashkey, - 'barcode' => $product->barcode, - 'qrcode' => $qrcode, - 'category' => $product->category, - 'available' => $available, - ]; - }); - - return response()->json($products); - } - - public function viewProductwithAddStoreData() - { - $hashfromRequest = request()->input('target'); - - - if (!$hashfromRequest || is_numeric($hashfromRequest)) { - return ResponseHelper::returnFalseOrNull($hashfromRequest); - } - return self::viewProductDetails($hashfromRequest, true); - } - - public function AddProducttoStore() - { - /** @var User $user */ - $user = Auth::user(); - if (!$user) { - return ResponseHelper::returnUnauthorized(); - } - - $storeHash = request()->input('TargetStore'); - $productHash = request()->input('target'); - - if (!$storeHash || !$productHash) { - return ResponseHelper::returnIncorrectDetails(); - } - - // Check both global and own-store permissions - $allowedAny = ProductPermissions::isActionAllowed(userActions::AddProducttoAnyStore); - if (!$allowedAny) { - $allowedOwn = ProductPermissions::isActionAllowed(userActions::AddProducttoOwnStore, $productHash, $storeHash); - if (!$allowedOwn) { - return ResponseHelper::returnUnauthorized(); - } - } - - try { - /** @var Store $store */ - $storeObj = Store::where('hashkey', $storeHash)->firstOrFail(); - /** @var Product $product */ - $productObj = Product::where('hashkey', $productHash)->firstOrFail(); - } catch (ModelNotFoundException $e) { - return ResponseHelper::returnFalseOrNull('Store or Product not found'); - } - - try { - StoreController::attachProducttoStore($storeObj, $productObj); - return ResponseHelper::returnSuccessResponse([], $productObj); - } catch (\Throwable $th) { - if (str_contains($th->getMessage(), 'Duplicate entry')) { - return ResponseHelper::returnError('Already Added to Store'); - } - return ResponseHelper::returnError('Failed to add product to store: ' . $th->getMessage()); - } - } - - public function RemoveProductFromStore() - { - /** @var User $user */ - $user = Auth::user(); - if (!$user) { - return ResponseHelper::returnUnauthorized(); - } - - $storeHash = request()->input('TargetStore'); - $productHash = request()->input('target'); - - if (!$storeHash || !$productHash) { - return ResponseHelper::returnIncorrectDetails(); - } - - try { - $store = Store::where('hashkey', $storeHash)->firstOrFail(); - $product = Product::where('hashkey', $productHash)->firstOrFail(); - } catch (ModelNotFoundException $e) { - return ResponseHelper::returnFalseOrNull('Store or Product not found'); - } - - $acctType = $user->acct_type instanceof UserTypes ? $user->acct_type : UserTypes::tryFrom($user->acct_type); - $isBig3 = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR]); - - $isAllowedAccess = false; - if ($isBig3) { - $isAllowedAccess = true; - } elseif ($store->owner_id === $user->id || $store->manager_id === $user->id || $store->managers()->where('user_id', $user->id)->exists()) { - $isAllowedAccess = true; - } elseif (UserPermissions::isDescendantOfCurrentUser($store->owner_id)) { - $isAllowedAccess = true; - } else { - foreach ($store->managerUsers()->pluck('users.id')->toArray() as $managerId) { - if (UserPermissions::isDescendantOfCurrentUser($managerId)) { - $isAllowedAccess = true; - break; - } - } - } - - if (!$isAllowedAccess) { - return ResponseHelper::returnUnauthorized(); - } - - try { - if ($store->products()->where('prd_items.id', $product->id)->exists()) { - $store->products()->detach($product->id); - return ResponseHelper::returnSuccessResponse([], $product); - } else { - return ResponseHelper::returnError('Product not attached to this store'); - } - } catch (\Throwable $th) { - return ResponseHelper::returnFalseOrNull($th->getMessage()); - } - } - - /** - * Return the set of store hashes (from the user's selectable stores) where this product is currently assigned. - */ - public function getAssignedStoresForProduct() - { - /** @var User $user */ - $user = Auth::user(); - if (!$user) { - return ResponseHelper::returnUnauthorized(); - } - - $productHash = request()->input('target'); - if (!$productHash) { - return ResponseHelper::returnIncorrectDetails(); - } - - try { - $product = Product::where('hashkey', $productHash)->firstOrFail(); - } catch (ModelNotFoundException $e) { - return ResponseHelper::returnFalseOrNull('Product not found'); - } - - $storeHashes = $product->stores()->pluck('str.hashkey')->toArray(); - - return response()->json([ - 'success' => true, - 'data' => $storeHashes, - ]); - } - - - /** - * Assign a product to a store with ownership-aware permissions. - * - Ultimate/SuperOperator/Operator: can assign to any store - * - Store Owner: can assign to stores they own - * - Store Manager: can assign to stores they manage - */ - public function AssignProductToOwnStore() - { - /** @var User $user */ - $user = Auth::user(); - if (!$user) { - return ResponseHelper::returnUnauthorized(); - } - $acctType = $user->acct_type instanceof UserTypes ? $user->acct_type : UserTypes::tryFrom($user->acct_type); - $isBig3 = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR]); - $storeHash = request()->input('TargetStore'); - $productHash = request()->input('target'); - - $price = request()->input('price'); - $available = request()->input('available'); - $description = request()->input('description'); - - if (!$storeHash || !$productHash) { - return ResponseHelper::returnIncorrectDetails(); - } - - try { - $store = Store::where('hashkey', $storeHash)->firstOrFail(); - $product = Product::where('hashkey', $productHash)->firstOrFail(); - } catch (ModelNotFoundException $e) { - return ResponseHelper::returnFalseOrNull('Store or Product not found'); - } - - // Big 3 always allowed. - $isAllowedAccess = false; - if ($isBig3) { - $isAllowedAccess = true; - } else { - if ($store->owner_id === $user->id || $store->manager_id === $user->id || $store->managers()->where('user_id', $user->id)->exists()) { - $isAllowedAccess = true; - } else { - if (UserPermissions::isDescendantOfCurrentUser($store->owner_id)) { - $isAllowedAccess = true; - } else { - $managerIds = $store->managerUsers()->pluck('users.id')->toArray(); - foreach ($managerIds as $managerId) { - if (UserPermissions::isDescendantOfCurrentUser($managerId)) { - $isAllowedAccess = true; - break; - } - } - } - } - } - - if (!$isAllowedAccess) { - return ResponseHelper::returnUnauthorized(); - } - - try { - /** @var Store $store */ - /** @var Product $product */ - $pivotData = [ - 'available' => (int) ($available ?? 0), - 'price' => (float) ($price ?? 0), - 'is_active' => true, - ]; - if ($description !== null && $description !== '') { - $pivotData['description'] = $description; - } - - $existing = $store->products()->where('prd_items.id', $product->id)->exists(); - if ($existing) { - $store->products()->updateExistingPivot($product->id, $pivotData); - } else { - $store->products()->attach($product->id, $pivotData); - } - - return ResponseHelper::returnSuccessResponse( - ['store_hash' => $storeHash, 'product_hash' => $productHash], - $product->hashkey, - 'Product assigned to store successfully' - ); - } catch (\Throwable $th) { - return ResponseHelper::returnError('Failed to assign product to store: ' . $th->getMessage()); - } - } - - - public function listProducts_Admin(Request $request) - { - /** @var User $user */ - $user = Auth::user(); - if (!$user) { - return ResponseHelper::returnUnauthorized(); - } - - $acctType = $user->acct_type instanceof UserTypes ? $user->acct_type : UserTypes::tryFrom($user->acct_type); - $isBig3 = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR]); - - if ($isBig3) { - $products = Product::select(['id', 'hashkey', 'name', 'price', 'unitname', 'available', 'is_active', 'photourl', 'category', 'subcategory', 'created_by']) - ->orderBy('id', 'desc') - ->get(); - } else { - // Non-big 3 users see their own and their descendants' products, - // plus any products assigned to stores they own or manage. - /** @var User $user */ - $allowedIds = array_merge([$user->id], $user->getAllDescendants()->pluck('id')->toArray()); - - $myStoreIds = Store::where(function ($q) use ($allowedIds) { - $q->whereIn('owner_id', $allowedIds) - ->orWhereIn('manager_id', $allowedIds) - ->orWhereHas('managers', function ($mq) use ($allowedIds) { - $mq->whereIn('user_id', $allowedIds); - }); - })->pluck('id')->toArray(); - - $assignedProductIds = DB::table('prd_str') - ->whereIn('store_id', $myStoreIds) - ->pluck('product_id') - ->toArray(); - - $products = Product::where(function ($q) use ($allowedIds, $assignedProductIds) { - $q->whereIn('created_by', $allowedIds) - ->orWhereIn('id', $assignedProductIds); - }) - ->select(['id', 'hashkey', 'name', 'price', 'unitname', 'available', 'is_active', 'photourl', 'category', 'subcategory', 'created_by']) - ->orderBy('id', 'desc') - ->get(); - } - - return response()->json([ - 'success' => true, - 'products' => $products - ]); - } - - public function listGlobalProductsForPicker(Request $request) - { - /** @var User $user */ - $user = Auth::user(); - if (!$user) { - return ResponseHelper::returnUnauthorized(); - } - - $products = Product::where('is_active', true) - ->select(['id', 'hashkey', 'name', 'price', 'unitname', 'available', 'is_active', 'photourl', 'category', 'subcategory', 'description']) - ->orderBy('name', 'asc') - ->get(); - - return response()->json([ - 'success' => true, - 'products' => $products, - ]); - } - - public function deleteProduct_Admin(Request $request) - { - $user = Auth::user(); - $isUltimate = $user && $user->acct_type === UserTypes::ULTIMATE; - - $canDeleteAny = ProductPermissions::isActionAllowed(UserActions::DeleteAllProducts); - $canDeleteOwn = ProductPermissions::isActionAllowed(UserActions::DeleteOwnProduct); - if (!$canDeleteAny && !$canDeleteOwn) { - return ResponseHelper::returnUnauthorized(); - } - - $hash = $request->input('target'); - if (!$hash) { - return ResponseHelper::returnIncorrectDetails(); - } - - try { - $product = Product::where('hashkey', $hash)->firstOrFail(); - - // PERMISSION CHECK: If not ultimate, must be the creator (or a - // descendant ancestor of the creator). - if (!$isUltimate && (int) $product->created_by !== (int) $user->id) { - if (!UserPermissions::isDescendantOfCurrentUser((int) $product->created_by)) { - return ResponseHelper::returnUnauthorized('You can only delete products you created.'); - } - } - - $product->delete(); - return ResponseHelper::returnSuccessResponse([], $hash, 'Product deleted successfully'); - } catch (Exception $e) { - return ResponseHelper::returnError('Failed to delete product: ' . $e->getMessage()); - } - } - - public function toggleProductStatus_Admin(Request $request) - { - $user = Auth::user(); - $isUltimate = $user && $user->acct_type === UserTypes::ULTIMATE; - - $hash = $request->input('target'); - $data = $request->input('data') ?? []; - $storeHash = $data['store_hash'] ?? null; - - if (!$hash) { - return ResponseHelper::returnIncorrectDetails(); - } - - // Toggling a per-store override is gated by store ownership below. - // Toggling the global product flag requires ModifyAllProducts. - $canModifyGlobal = ProductPermissions::isActionAllowed(UserActions::ModifyAllProducts); - $canModifyOwnStoreProduct = ProductPermissions::isActionAllowed(UserActions::ModifyOwnProduct) - || ProductPermissions::isActionAllowed(UserActions::AddProducttoOwnStore); - - if (!$canModifyGlobal && !($storeHash && $canModifyOwnStoreProduct)) { - return ResponseHelper::returnUnauthorized(); - } - - try { - $product = Product::where('hashkey', $hash)->firstOrFail(); - - if ($storeHash) { - $store = Store::where('hashkey', $storeHash)->first(); - if ($store) { - $isOwnStore = (int) $store->owner_id === (int) $user->id || (int) $store->manager_id === (int) $user->id; - $isDescendant = false; - if (!$isUltimate && !$isOwnStore) { - $descendants = $user->getAllDescendants(); - $descendantIds = $descendants->pluck('id')->toArray(); - $isDescendant = in_array($store->owner_id, $descendantIds) || in_array($store->manager_id, $descendantIds); - } - - if ($isUltimate || $isOwnStore || $isDescendant) { - $pivot = $product->stores()->where('store_id', $store->id)->first(); - $currentStatus = $pivot ? $pivot->pivot->is_active : true; - - $product->stores()->syncWithoutDetaching([ - $store->id => [ - 'is_active' => !$currentStatus, - ] - ]); - - return ResponseHelper::returnSuccessResponse( - ['is_active' => !$currentStatus], - $hash, - 'Store-specific product status updated successfully' - ); - } - } - } - - // PERMISSION CHECK: If not ultimate, must be the creator - if (!$isUltimate && (int) $product->created_by !== (int) $user->id) { - return ResponseHelper::returnUnauthorized('You can only modify products you created.'); - } - - $product->is_active = !$product->is_active; - $product->save(); - - return ResponseHelper::returnSuccessResponse( - ['is_active' => $product->is_active], - $hash, - 'Product status updated successfully' - ); - } catch (Exception $e) { - return ResponseHelper::returnError('Failed to update product status: ' . $e->getMessage()); - } - } - - /** - * Fuzzy-search global products by name so users can choose to import an existing - * product into their store instead of creating a duplicate. - */ - public function fuzzySearchByName(Request $request) - { - $user = Auth::user(); - if (!$user) { - return ResponseHelper::returnUnauthorized(); - } - - $canCreateGlobal = ProductPermissions::isActionAllowed(UserActions::CreateProductGlobal); - $canCreateForOwnStore = ProductPermissions::isActionAllowed(UserActions::CreateProductForOwnStore); - if (!$canCreateGlobal && !$canCreateForOwnStore) { - return ResponseHelper::returnUnauthorized(); - } - - $name = trim((string) $request->input('name', '')); - $storeHash = $request->input('TargetStore'); - $limit = 10; - - if ($name === '') { - return response()->json(['success' => true, 'data' => []]); - } - - $normalized = strtolower($name); - $tokens = array_values(array_filter(preg_split('/\s+/', $normalized) ?: [], fn ($t) => strlen($t) >= 2)); - - $query = Product::query() - ->select(['id', 'hashkey', 'name', 'price', 'unitname', 'category', 'subcategory', 'photourl', 'description']) - ->where('is_active', 1); - - $query->where(function ($q) use ($normalized, $tokens) { - $q->whereRaw('LOWER(name) LIKE ?', ['%' . $normalized . '%']) - ->orWhereRaw('SOUNDEX(name) = SOUNDEX(?)', [$normalized]); - foreach ($tokens as $tok) { - $q->orWhereRaw('LOWER(name) LIKE ?', ['%' . $tok . '%']); - } - }); - - $candidates = $query->limit(50)->get(); - - // Score candidates by simple similarity; rank desc. - $scored = $candidates->map(function ($p) use ($normalized) { - $candidate = strtolower((string) $p->name); - similar_text($normalized, $candidate, $percent); - $contains = str_contains($candidate, $normalized) ? 15 : 0; - $p->_score = $percent + $contains; - return $p; - })->filter(fn ($p) => $p->_score >= 45) - ->sortByDesc('_score') - ->values() - ->take($limit); - - $alreadyInStoreIds = []; - if ($storeHash) { - $store = Store::where('hashkey', $storeHash)->first(); - if ($store) { - $alreadyInStoreIds = $store->products()->pluck('prd_items.id')->toArray(); - } - } - - $payload = $scored->map(function ($p) use ($alreadyInStoreIds) { - return [ - 'hashkey' => $p->hashkey, - 'name' => $p->name, - 'price' => $p->price, - 'unitname' => $p->unitname, - 'category' => $p->category, - 'subcategory' => $p->subcategory, - 'photourl' => $p->photourl, - 'description' => $p->description, - 'score' => round($p->_score, 1), - 'already_in_store' => in_array($p->id, $alreadyInStoreIds, true), - ]; - }); - - return response()->json(['success' => true, 'data' => $payload]); - } -} - - - diff --git a/app/Http/Controllers/Market/ProductPhotoSearchController.php b/app/Http/Controllers/Market/ProductPhotoSearchController.php deleted file mode 100644 index 8926b60..0000000 --- a/app/Http/Controllers/Market/ProductPhotoSearchController.php +++ /dev/null @@ -1,190 +0,0 @@ - [ - 'method' => 'GET', - 'header' => implode("\r\n", self::DDG_BROWSER_HEADERS), - 'timeout' => 10, - ]]); - $html = @file_get_contents($url, false, $ctx); - if (!$html) return null; - // The vqd token appears in the page JS in several formats depending on - // DDG's current build. Try the quoted forms first (vqd="4-xxx" / - // vqd='4-xxx' / vqd:"4-xxx"), then fall back to the bare form. - $patterns = [ - '/vqd=["\']([0-9a-zA-Z._\-]+)["\']/', // vqd="4-123..." or vqd='4-123...' - '/vqd["\']?\s*[:=]\s*["\']([0-9a-zA-Z._\-]+)["\']/', // vqd:"4-123..." - '/vqd=([0-9a-zA-Z._\-]+)/', // bare vqd=4-123... - ]; - foreach ($patterns as $pattern) { - if (preg_match($pattern, $html, $m) && !empty($m[1])) { - return $m[1]; - } - } - return null; - } - - // GET /api/products/photo-search?q=...&page=1 - public function search(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::SearchStockPhotos)) { - return response()->json(['error' => 'Unauthorized'], 403); - } - - $query = trim($request->input('q', '')); - $page = max(1, (int) $request->input('page', 1)); - - if (!$query) { - return response()->json(['error' => 'Query required'], 422); - } - - $vqd = self::getDdgVqd($query); - if (!$vqd) { - return response()->json(['error' => 'Could not reach image search service'], 502); - } - - // s = offset; DDG returns ~15 results per call; page 1 = s=0, page 2 = s=15, etc. - $offset = ($page - 1) * 15; - - $url = 'https://duckduckgo.com/i.js?' . http_build_query([ - 'q' => $query, - 'vqd' => $vqd, - 'o' => 'json', - 'p' => '1', - 'f' => ',,,', - 'l' => 'us-en', - 's' => $offset, - ]); - - $ctx = stream_context_create(['http' => [ - 'method' => 'GET', - 'header' => implode("\r\n", self::DDG_BROWSER_HEADERS), - 'timeout' => 10, - ]]); - - $raw = @file_get_contents($url, false, $ctx); - if ($raw === false) { - return response()->json(['error' => 'Failed to fetch image results'], 502); - } - - $data = json_decode($raw, true); - $results = $data['results'] ?? []; - - $photos = array_map(fn($r) => [ - 'id' => md5($r['image']), // stable ID from image URL - 'thumb' => $r['thumbnail'], // DDG-proxied small thumb (safe to display) - 'src' => $r['image'], // actual source image URL (used for download) - 'title' => $r['title'] ?? '', - ], array_slice($results, 0, 15)); - - return response()->json([ - 'success' => true, - 'photos' => $photos, - 'page' => $page, - 'has_more' => count($results) >= 15, - ]); - } - - // POST /api/products/photo-download - // body: { src: "https://..." } — the actual source image URL from DDG results - public function download(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::DownloadStockPhoto)) { - return response()->json(['error' => 'Unauthorized'], 403); - } - - $src = $request->input('src', ''); - - // SSRF guard: must be http/https and must not target private/loopback IPs - $parsed = parse_url($src); - $scheme = $parsed['scheme'] ?? ''; - $host = strtolower($parsed['host'] ?? ''); - - if (!in_array($scheme, ['http', 'https']) || !$host) { - return response()->json(['error' => 'Invalid URL'], 422); - } - - // Block private/loopback ranges - if (preg_match('/^(localhost|127\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|0\.0\.0\.0|::1)/i', $host)) { - return response()->json(['error' => 'Forbidden URL'], 403); - } - - $ctx = stream_context_create(['http' => [ - 'method' => 'GET', - 'header' => 'User-Agent: Mozilla/5.0' . "\r\n", - 'timeout' => 15, - ]]); - $raw = @file_get_contents($src, false, $ctx); - if ($raw === false || strlen($raw) < 500) { - return response()->json(['error' => 'Failed to fetch image'], 502); - } - - // Resize to max 1280x720 using PHP GD (bundled — no Intervention Image needed) - $srcImg = @imagecreatefromstring($raw); - if (!$srcImg) { - return response()->json(['error' => 'Invalid image data'], 422); - } - - $origW = imagesx($srcImg); - $origH = imagesy($srcImg); - $maxW = 1280; - $maxH = 720; - - $ratio = min($maxW / $origW, $maxH / $origH, 1.0); // never upscale - $newW = (int) round($origW * $ratio); - $newH = (int) round($origH * $ratio); - - $dstImg = imagescale($srcImg, $newW, $newH, IMG_BILINEAR_FIXED); - imagedestroy($srcImg); - - ob_start(); - imagejpeg($dstImg, null, 85); - $binary = ob_get_clean(); - imagedestroy($dstImg); - - // Save via existing pipeline — binary string branch in uploadFileContent handles this - $result = FilesMainController::uploadFileList( - $binary, - 'stock-photo', - 'stock_photo_' . time() . '.jpg', - '', - [], - 'ProductMarket', - [], - 0, - 'image/jpeg' - ); - - if (!$result || empty($result->hashkey)) { - return response()->json(['error' => 'Save failed'], 500); - } - - return response()->json([ - 'success' => true, - 'hashkey' => $result->hashkey, - 'url' => $result->resolvedUrl(), - ]); - } -} diff --git a/app/Http/Controllers/Market/ShipmentController.php b/app/Http/Controllers/Market/ShipmentController.php deleted file mode 100644 index c49d544..0000000 --- a/app/Http/Controllers/Market/ShipmentController.php +++ /dev/null @@ -1,163 +0,0 @@ -acct_type, UserActions::ViewShipments)) { - return ResponseHelper::returnUnauthorized(); - } - - $user = Auth::user(); - - $query = Shipment::with(['courier', 'transaction', 'store', 'customer']); - - // filter by store if provided - if ($storeHash = $request->input('store_hash')) { - $store = Store::where('hashkey', $storeHash)->first(); - if ($store) { - $query->where('store_id', $store->id); - } - } - - // if not ultimate/admin, restrict to user's shipments - // (This logic might need adjustment based on how roles are defined) - // For now, let's just list all and allow filtering - - $shipments = $query->orderBy('created_at', 'desc')->get(); - - return response()->json([ - 'success' => true, - 'data' => $shipments - ]); - } - - public function createNewShipment(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::CreateShipment)) { - return ResponseHelper::returnUnauthorized(); - } - - $user = Auth::user(); - - $validated = $request->validate([ - 'transaction_hash' => 'required|string', - 'store_hash' => 'nullable|string', - 'customer_hash' => 'nullable|string', - 'courier_hash' => 'nullable|string', - 'origin_address' => 'nullable|string', - 'destination_address' => 'nullable|string', - 'shipping_fee' => 'nullable|numeric', - 'estimated_delivery_date' => 'nullable|date', - ]); - - $transaction = GlobalTransaction::where('hashkey', $validated['transaction_hash'])->first(); - if (!$transaction) { - return ResponseHelper::returnError('Transaction not found', 404); - } - - $store = $validated['store_hash'] ? Store::where('hashkey', $validated['store_hash'])->first() : null; - $customer = $validated['customer_hash'] ? Customer::where('hashkey', $validated['customer_hash'])->first() : null; - $courier = $validated['courier_hash'] ? Courier::where('hashkey', $validated['courier_hash'])->first() : null; - - $shipment = new Shipment([ - 'transaction_id' => $transaction->id, - 'store_id' => $store?->id, - 'customer_id' => $customer?->id, - 'courier_id' => $courier?->id, - 'origin_address' => $validated['origin_address'] ?? $store?->address, - 'destination_address' => $validated['destination_address'] ?? $customer?->address, - 'shipping_fee' => $validated['shipping_fee'] ?? 0, - 'estimated_delivery_date' => $validated['estimated_delivery_date'], - 'status' => 'PENDING', - 'created_by' => $user->id, - ]); - - if ($shipment->save()) { - return ResponseHelper::returnSuccessResponse($shipment, $shipment->hashkey, 'Shipment created successfully'); - } - - return ResponseHelper::returnError('Failed to create shipment'); - } - - public function updateShipmentStatus(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::UpdateShipmentStatus)) { - return ResponseHelper::returnUnauthorized(); - } - - $hashkey = $request->input('target'); - $status = $request->input('status'); - - if (!$hashkey || !$status) { - return ResponseHelper::returnIncorrectDetails(); - } - - $shipment = Shipment::where('hashkey', $hashkey)->first(); - if (!$shipment) { - return ResponseHelper::returnError('Shipment not found', 404); - } - - $shipment->status = $status; - if ($status === 'DELIVERED') { - $shipment->actual_delivery_date = now(); - } - $shipment->save(); - - return ResponseHelper::returnSuccessResponse($shipment, $shipment->hashkey, 'Shipment status updated'); - } - - public function listCouriers() - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewCouriers)) { - return ResponseHelper::returnUnauthorized(); - } - - $couriers = Courier::where('is_active', true)->get(); - return response()->json([ - 'success' => true, - 'data' => $couriers - ]); - } - - public function createCourier(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::CreateCourier)) { - return ResponseHelper::returnUnauthorized(); - } - - $validated = $request->validate([ - 'name' => 'required|string|max:255', - 'contact_number' => 'nullable|string', - 'type' => 'required|string|in:INTERNAL,EXTERNAL', - ]); - - $courier = new Courier([ - 'name' => $validated['name'], - 'contact_number' => $validated['contact_number'], - 'type' => $validated['type'], - ]); - - if ($courier->save()) { - return ResponseHelper::returnSuccessResponse($courier, $courier->hashkey, 'Courier created'); - } - - return ResponseHelper::returnError('Failed to create courier'); - } -} diff --git a/app/Http/Controllers/Market/StoreController.php b/app/Http/Controllers/Market/StoreController.php deleted file mode 100644 index 05e3cb5..0000000 --- a/app/Http/Controllers/Market/StoreController.php +++ /dev/null @@ -1,1510 +0,0 @@ -json($categories); - } - - return $categories; - } - - public function getSubcategories(Request|false $request = false, string|false $category = false) - { - $request = $request ?: request(); - $category = $request ? $request->input('category') : $category; - - $categoryList = Config::get('market.storesCategorySubcategory', []); - - if (!$category || !array_key_exists($category, $categoryList)) { - if ($request) { - return response()->json([], 400); - } - return []; - } - - $subcategories = $categoryList[$category] ?? []; - - if ($request) { - return response()->json($subcategories); - } - - return $subcategories; - } - - public function store(Request $request) - { - $validator = Validator::make($request->all(), [ - 'name' => 'required|string|max:255|unique:str,name', - 'description' => 'required|string', - 'address' => 'required|string', - 'category' => 'nullable|string|max:100', - 'subcategory' => 'nullable|string|max:100', - 'remarks' => 'nullable|string', - 'photourl' => 'nullable|array', - 'files' => 'nullable|array', - 'owner' => 'nullable|string', - 'manager' => 'nullable|string', - 'managers' => 'nullable|array', - 'cooperatives' => 'nullable|array', - 'cooperatives.*' => 'string', - ], [ - 'name.unique' => 'A store with this name already exists. Please choose a different name.', - ]); - - if ($validator->fails()) { - return response()->json([ - 'success' => false, - 'message' => $validator->errors()->first(), - 'errors' => $validator->errors() - ], 422); - } - - $validated = $validator->validated(); - - $storeCode = $request->input('storecode') - ?? self::generateStoreCode($request->input('category', 'General')); - - /** @var User $currentUser */ - $currentUser = Auth::user(); - if (!$currentUser) { - return ResponseHelper::returnUnauthorized(); - } - - $acctTypeEarly = $currentUser->acct_type instanceof UserTypes ? $currentUser->acct_type : UserTypes::tryFrom($currentUser->acct_type); - $isStoreOwner = $acctTypeEarly === UserTypes::STORE_OWNER; - - // STORE_OWNER: force owner to self, ignore any client-supplied owner/manager - if ($isStoreOwner) { - $ownerId = $currentUser->id; - $managerId = $currentUser->id; - } else { - $ownerId = ($validated['owner'] ?? null) ? UserController::findUserIdByHash($validated['owner']) : null; - $managerId = ($validated['manager'] ?? null) ? UserController::findUserIdByHash($validated['manager']) : null; - } - - $acctType = $acctTypeEarly; - $isBig3 = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR]); - - // RBAC: only Big3 or STORE_OWNER may create stores - if (!$isBig3 && !$isStoreOwner) { - return response()->json(['success' => false, 'message' => 'You are not allowed to create stores.'], 403); - } - - if (!$isBig3 && !$isStoreOwner && $ownerId) { - $isParent = UserPermissions::IsParentofTargetUser($validated['owner']); - if (!$isParent && Auth::id() !== $ownerId) { - return response()->json(['success' => false, 'message' => 'Unauthorized to create store for this owner'], 403); - } - } - - // STORE_OWNER cannot override status — always active - $status = $isStoreOwner ? 'active' : $request->input('status', 'active'); - - $data = [ - 'storecode' => $storeCode, - 'name' => $validated['name'], - 'description' => $validated['description'], - 'status' => $status, - 'remarks' => $isStoreOwner ? '' : ($validated['remarks'] ?? ''), - 'category' => $validated['category'] ?? 'General', - 'subcategory' => $validated['subcategory'] ?? '', - 'photourl' => $validated['photourl'] ?? $validated['files'] ?? '', - 'address' => $validated['address'], - 'owner_id' => $ownerId, - 'manager_id' => $managerId, - 'created_by' => Auth::id(), - 'is_active' => true, - ]; - - $store = Store::create($data); - - // STORE_OWNER auto-self-assigns as manager - if ($isStoreOwner) { - \App\Models\Market\StoreManager::create([ - 'store_id' => $store->id, - 'user_id' => $currentUser->id, - 'created_by' => $currentUser->id, - ]); - } - - // Handle multiple managers. For STORE_OWNER, restrict to STORE_MANAGER descendants. - if ($request->filled('managers') && is_array($request->input('managers'))) { - $descendantIds = null; - if ($isStoreOwner) { - $descendantIds = $currentUser->getAllDescendants()->pluck('id')->toArray(); - } - - foreach ($request->input('managers') as $mgrHash) { - $mgrId = UserController::findUserIdByHash($mgrHash); - if (!$mgrId) { - continue; - } - - if ($isStoreOwner) { - if (!in_array($mgrId, $descendantIds, true)) { - continue; - } - $mgrUser = User::find($mgrId); - $mgrType = $mgrUser?->acct_type instanceof UserTypes ? $mgrUser->acct_type : UserTypes::tryFrom($mgrUser?->acct_type ?? ''); - if ($mgrType !== UserTypes::STORE_MANAGER) { - continue; - } - if ($mgrId === $currentUser->id) { - continue; // already added above - } - } - - \App\Models\Market\StoreManager::create([ - 'store_id' => $store->id, - 'user_id' => $mgrId, - 'created_by' => Auth::id(), - ]); - } - } - - // Sync cooperative links (optional, many-to-many) — admins only - if (!$isStoreOwner) { - $this->syncStoreCooperatives($store, $request->input('cooperatives', [])); - } - - return Response::json(['success' => true, 'hashkey' => $store->hashkey], 201); - } - - /** - * One-click store creation: assigns the current user as both owner - * and sole manager, with sensible defaults. Used by the Store Owner home. - */ - public function autoCreate(Request $request) - { - /** @var User $currentUser */ - $currentUser = Auth::user(); - if (!$currentUser) { - return ResponseHelper::returnUnauthorized(); - } - - $acctType = $currentUser->acct_type instanceof UserTypes - ? $currentUser->acct_type - : UserTypes::tryFrom($currentUser->acct_type); - $isBig3 = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR]); - $isStoreOwner = $acctType === UserTypes::STORE_OWNER; - - if (!$isBig3 && !$isStoreOwner) { - return response()->json(['success' => false, 'message' => 'You are not allowed to create stores.'], 403); - } - - $displayName = $currentUser->nickname ?: ($currentUser->name ?: 'My'); - $name = trim((string) $request->input('name')) ?: ($displayName . "'s Store"); - $description = trim((string) $request->input('description')) ?: 'Auto-created store'; - $address = trim((string) $request->input('address')) ?: 'Address not set'; - $category = $request->input('category', 'General'); - - // `str.name` is globally unique; if the auto-generated name collides - // (two owners sharing a nickname), append a short suffix until free. - $baseName = $name; - $attempt = 0; - while (Store::where('name', $name)->exists()) { - $attempt++; - if ($attempt > 20) { - return response()->json([ - 'success' => false, - 'message' => 'Could not generate a unique store name. Please use Custom Create.', - ], 409); - } - $name = $baseName . ' #' . strtoupper(substr(bin2hex(random_bytes(2)), 0, 4)); - } - - $store = Store::create([ - 'storecode' => self::generateStoreCode($category), - 'name' => $name, - 'description' => $description, - 'status' => 'active', - 'remarks' => '', - 'category' => $category, - 'subcategory' => '', - 'photourl' => '', - 'address' => $address, - 'owner_id' => $currentUser->id, - 'manager_id' => $currentUser->id, - 'created_by' => $currentUser->id, - 'is_active' => true, - ]); - - \App\Models\Market\StoreManager::create([ - 'store_id' => $store->id, - 'user_id' => $currentUser->id, - 'created_by' => $currentUser->id, - ]); - - return Response::json([ - 'success' => true, - 'hashkey' => $store->hashkey, - 'name' => $store->name, - ], 201); - } - - public static function generateStoreCode(string $category, ?int $num = null, int $length = 8): string - { - $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'; - $charactersLength = strlen($characters); - - - $num = $num ?? rand(1, 99); - - - $randomString = ''; - for ($i = 0; $i < $length; $i++) { - $randomString .= $characters[rand(0, $charactersLength - 1)]; - } - - $categoryPrefix = substr($category, 0, 2); - $categorySuffix = substr($category, -2, 2); - $cat = strtoupper($categoryPrefix . $categorySuffix); - - return date('Y') . '-' . $cat . '-' . $num . '-' . $randomString; - } - - public static function viewStoreDetailsLegacy(Request|false $request = false, string|false $target = false) - { - try { - if ($target) { - $hash = $target; - } else { - $hash = $request->input('target'); - } - if (!$hash) { - return $target ? false : response()->json(['success' => false]); - } - - $store = Store::where('hashkey', $hash)->first(); - - if (!$store) { - return $target ? false : response()->json(['success' => false]); - } - - $products = Product::where('store_id', $store->id) - ->select('name', 'hashkey', 'price', 'unitname', 'available', 'is_active as status', 'photourl') - ->get() - ->filter(fn($product) => $product->available) - ->map(function ($product) { - $photourl = $product->photourl; - if ($photourl && (is_array($photourl) || is_object($photourl))) { - $product->photourl = (array)$photourl; - } elseif (is_string($photourl)) { - $product->photourl = json_decode($photourl, true) ?: []; - } else { - $product->photourl = []; - } - return $product; - }) - ->values(); - - $store->products = $products; - - if ($target) { - return $store; - } else { - return response()->json(['success' => true, 'data' => $store]); - } - } catch (\Exception $e) { - return response()->json(['success' => false, 'message' => $e->getMessage()], 500); - } - } - - public static function viewStoreDetails(Request|false $request = false, string|false $target = false) - { - - $req = $request ?: request(); - - $hash = $target ?: ($req?->input('target') ?? null); - - if (!$hash) { - return $target ? false : response()->json(['success' => false]); - } - - // Fetch the store by hashkey - $store = Store::where('hashkey', $hash) - ->select( - 'id', - 'photourl', - 'hashkey', - 'name', - 'category', - 'subcategory', - 'created_at as created', - 'updated_at as modified', - 'description', - 'is_active as status', - 'address', - 'owner_id', - 'manager_id' - ) - ->first(); - - if (!$store) { - return $target ? false : response()->json(['success' => false]); - } - - - - /** @var User $user */ - $user = Auth::user(); - $products = $store->products() - ->select( - - 'prd_items.name', - 'prd_items.hashkey', - 'prd_str.price as store_price', - 'prd_items.price', - 'prd_items.unitname', - 'prd_str.available', - 'prd_str.is_active as status', - 'prd_items.photourl' - ) - ->get() - //TODO Decide later wheether to include unavailable store products - // ->filter(fn($product) => $product->available) - ->map(function ($product) { - $photourl = $product->photourl; - if ($photourl && is_array($photourl)) { - $product->photourl = $photourl; - } elseif (is_string($photourl)) { - $product->photourl = json_decode($photourl, true) ?: []; - } else { - $product->photourl = []; - } - - return $product; - }) - ->values(); - - $store->products = $products; - - // Resolve store photo URLs: prefer cdn_url, fall back to /RequestData/File/{hash} - $rawPhotos = $store->photourl ?? []; - if (!empty($rawPhotos) && is_array($rawPhotos)) { - $store->resolved_photos = \App\Models\FileList::whereIn('hashkey', $rawPhotos) - ->get(['hashkey', 'cdn_url']) - ->keyBy('hashkey') - ->pipe(function ($fileMap) use ($rawPhotos) { - return collect($rawPhotos)->map(function ($hash) use ($fileMap) { - $file = $fileMap->get($hash); - if (!$file) return null; - $cdn = trim((string)($file->cdn_url ?? '')); - return $cdn !== '' ? $cdn : "/RequestData/File/{$hash}"; - })->filter()->values()->toArray(); - }); - } else { - $store->resolved_photos = []; - } - - // Check if user can add products to this store - $store->can_add_product = ProductPermissions::isActionAllowed(UserActions::AddProducttoOwnStore, null, $store) - || ProductPermissions::isActionAllowed(UserActions::AddProducttoAnyStore, null, $store); - - // Check if user can assign existing products to this store - $store->can_assign_product = self::canUserAssignProduct($store); - - // Check if user can access POS for this store - $store->can_access_pos = self::canUserAccessPos($store); - - // Return the formatted response - return $target ? $store : response()->json(['success' => true, 'data' => $store]); - } - - /** - * Determine if the current user can assign existing products to a given store. - * Mirrors the permission check in ProductController::AssignProductToOwnStore. - */ - private static function canUserAssignProduct(Store $store): bool - { - /** @var User $user */ - $user = Auth::user(); - if (!$user) { - return false; - } - - $acctType = $user->acct_type->value ?? $user->acct_type ?? ''; - - if (in_array($acctType, ['ult', 'super operator', 'operator'])) { - return true; - } - - if ($store->owner_id === $user->id || $store->manager_id === $user->id) { - return true; - } - - if ($store->managers()->where('user_id', $user->id)->exists()) { - return true; - } - - if (UserPermissions::isDescendantOfCurrentUser($store->owner_id)) { - return true; - } - - $managerIds = $store->managerUsers()->pluck('users.id')->toArray(); - foreach ($managerIds as $managerId) { - if (UserPermissions::isDescendantOfCurrentUser($managerId)) { - return true; - } - } - - return false; - } - - /** - * Determine if the current user can access POS for a given store. - * Returns true if: - * - User is the store owner or manager - * - User is a direct/indirect parent of the store owner (via parentuid chain) - * - User has an admin role (ult, super operator, operator) - */ - private static function canUserAccessPos(Store $store): bool - { - /** @var User $user */ - $user = Auth::user(); - if (!$user) { - return false; - } - - $acctType = $user->acct_type->value ?? $user->acct_type ?? ''; - - // Big 3 always have access - if (in_array($acctType, ['ult', 'super operator', 'operator'])) { - return true; - } - - // Store owner - if ($store->owner_id === $user->id) { - return true; - } - - // Check if user is a store manager (primary or in the list) - if ($store->manager_id === $user->id) { - return true; - } - - if ($store->managers()->where('user_id', $user->id)->exists()) { - return true; - } - - // Check hierarchy: parent of owner or any manager - if (UserPermissions::isDescendantOfCurrentUser($store->owner_id)) { - return true; - } - - $managerIds = $store->managerUsers()->pluck('users.id')->toArray(); - foreach ($managerIds as $managerId) { - if (UserPermissions::isDescendantOfCurrentUser($managerId)) { - return true; - } - } - - return false; - } - - - - public static function editStoreDetails(Request|false $request = false, string|false $target = false) - { - - $req = $request ?: request(); - - $hash = $target ?: ($req?->input('target') ?? null); - - // file_put_contents('Storedetails.txt',$hash); - if (!$hash) { - return $target ? false : response()->json(['success' => false]); - } - - $store = Store::where('hashkey', $hash) - ->select( - 'photourl', - 'hashkey', - 'name', - 'category', - 'subcategory', - 'created_at as created', - 'updated_at as modified', - 'description', - 'photourl', - 'status', - 'is_active', - 'remarks', - 'address', - 'owner_id', - 'manager_id', - )->with('owner:hashkey,id') - ->with('manager:hashkey,id') - ->first(); - - if (!$store) { - return $target ? false : response()->json(['success' => false]); - } - - try { - $store->owner_hashkey = $store->owner->hashkey; - } catch (\Exception $th) { - $store->owner_hashkey = null; - } - - try { - $store->manager_hashkey = $store->manager->hashkey; - } catch (\Throwable $th) { - $store->manager_hashkey = 0; - } - - try { - $store->photourlDropzone = PhotoURL::photoURLArraytoDropzoneData($store->photourl); - } catch (\Throwable $th) { - $store->photourlDropzone = []; - } - - try { - $store->ParentList = CreateUserControllerUltimate::listAllUsersforParentSelectHTML(request(), true); - } catch (\Exception $e) { - $store->ParentList = []; - } - - // Include multi-manager hashkeys for frontend - try { - $store->managers_hashkeys = $store->managerUsers()->pluck('users.hashkey')->toArray(); - } catch (\Throwable $th) { - $store->managers_hashkeys = []; - } - - // Include linked cooperative hashkeys - try { - $store->cooperative_hashkeys = $store->cooperatives()->pluck('organizations.hashkey')->toArray(); - } catch (\Throwable $th) { - $store->cooperative_hashkeys = []; - } - - $products = $store->products() - ->select( - 'prd_items.name', - 'prd_items.hashkey', - 'prd_str.price', - 'prd_items.unitname', - 'prd_str.available', - 'prd_str.is_active as status', - 'prd_items.photourl' - ) - ->get() - ->filter(fn($product) => $product->available) - ->map(function ($product) { - $photourl = $product->photourl; - if ($photourl && is_array($photourl)) { - $product->photourl = $photourl; - } elseif (is_string($photourl)) { - $product->photourl = json_decode($photourl, true) ?: []; - } else { - $product->photourl = []; - } - return $product; - }) - ->values(); - - return $target ? $store : response()->json($store); - } - - public function update(Request $request) - { - $hashkey = request()->input('target'); - $store = Store::where('hashkey', $hashkey)->first(); - - if (!$store) { - return response()->json([ - 'success' => false, - 'message' => 'Store not found.' - ], 404); - } - - /** @var User $user */ - $user = Auth::user(); - if (!$user) { - return response()->json(['error' => 'Unauthorized'], 401); - } - - $acctType = $user->acct_type instanceof UserTypes ? $user->acct_type : UserTypes::tryFrom($user->acct_type); - $isBig3 = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR]); - - // Permission check: Big 3, parent of owner, direct owner, direct manager, or pivot manager - $isParentOfOwner = UserPermissions::isDescendantOfCurrentUser($store->owner_id); - $isDirectManager = $store->manager_id === $user->id; - $isManagerViaPivot = $store->managers()->where('user_id', $user->id)->exists(); - if (!$isBig3 && !$isParentOfOwner && $store->owner_id !== $user->id && !$isDirectManager && !$isManagerViaPivot) { - return response()->json(['error' => 'Unauthorized to modify this store'], 403); - } - - $storeId = $store->id; - $validator = Validator::make($request->all(), [ - 'name' => [ - 'required', 'string', 'max:255', - function ($attribute, $value, $fail) use ($storeId) { - $conflict = \App\Models\Market\Store::where('name', $value) - ->where('id', '!=', $storeId) - ->exists(); - if ($conflict) { - $fail('A store with this name already exists. Please choose a different name.'); - } - }, - ], - 'description' => 'required|string', - 'address' => 'required|string', - 'category' => 'nullable|string|max:100', - 'subcategory' => 'nullable|string|max:100', - 'remarks' => 'nullable|string', - 'photourl' => 'nullable|array', - 'files' => 'nullable|array', - 'owner' => 'nullable|string', - 'manager' => 'nullable|string', - 'managers' => 'nullable|array', - 'cooperatives' => 'nullable|array', - 'cooperatives.*' => 'string', - ]); - - if ($validator->fails()) { - return response()->json([ - 'success' => false, - 'errors' => $validator->errors() - ], 422); - } - - $validated = $validator->validated(); - - $ownerId = ($validated['owner'] ?? null) ? UserController::findUserIdByHash($validated['owner']) : $store->owner_id; - $managerId = ($validated['manager'] ?? null) ? UserController::findUserIdByHash($validated['manager']) : $store->manager_id; - - $status = $request->input('status', $store->status ?? 'active'); - - $data = [ - 'name' => $validated['name'], - 'description' => $validated['description'], - 'address' => $validated['address'], - 'remarks' => $validated['remarks'] ?? $store->remarks, - 'category' => $validated['category'] ?? $store->category, - 'subcategory' => $validated['subcategory'] ?? $store->subcategory, - 'status' => $status, - 'photourl' => $validated['photourl'] ?? $validated['files'] ?? $store->photourl, - 'owner_id' => $ownerId, - 'manager_id' => $managerId, - 'updated_by' => Auth::id(), - ]; - - $store->update($data); - - // Handle multiple managers - if ($request->filled('managers') && is_array($request->input('managers'))) { - // Remove existing managers not in the new list - $newMgrIds = []; - foreach ($request->input('managers') as $mgrHash) { - $mgrId = UserController::findUserIdByHash($mgrHash); - if ($mgrId) $newMgrIds[] = $mgrId; - } - - $store->managers()->whereNotIn('user_id', $newMgrIds)->delete(); - - // Add new ones - foreach ($newMgrIds as $mgrId) { - if (!$store->managers()->where('user_id', $mgrId)->exists()) { - \App\Models\Market\StoreManager::create([ - 'store_id' => $store->id, - 'user_id' => $mgrId, - 'created_by' => Auth::id(), - ]); - } - } - } - - // Sync cooperative links (only when key is present, allows clearing with empty array) - if ($request->has('cooperatives')) { - $this->syncStoreCooperatives($store, $request->input('cooperatives', []), true); - } - - return response()->json([ - 'success' => true, - 'message' => 'Store updated successfully.', - 'hashkey' => $store->hashkey, - ], 200); - } - - - /** - * Attach a store to a product with optional pivot data. - * - * This creates (or updates) a relationship between a given Product and Store - * in the `product_store` pivot table. It allows specifying stock quantity, - * price, and active status for that store-product link. - * - * If either the product or store cannot be found, the method safely returns false. - * - * @param \App\Models\Market\Product|int $product The Product model instance or its ID. - * @param \App\Models\Market\Store|int $store The Store model instance or its ID. - * @param int $available Quantity available for this product in the store (default: 0) - * @param int $price Price for this product in the store (default: 0) - * @param bool $is_active Whether the product is active in this store (default: true) - * @return bool Returns true on success, false if product/store not found or an exception occurs. - * - * @example - * ```php - * // Attach store #5 to product #12 with stock and price - * Product::attachStoreToProduct(12, 5, available: 40, price: 1499, is_active: true); - * - * // Using model instances - * $product = Product::find(12); - * $store = Store::find(5); - * Product::attachStoreToProduct($product, $store, 25, 1299); - * ``` - */ - public static function attachStoreToProduct(Product|int $product, Store|int $store, int $available = 0, int $price = 0, bool $is_active = true) - { - - if (is_int($product)) { - try { - $product = Product::findOrFail($product); - } catch (\Throwable $th) { - return false; - } - } - - - if (is_int($store)) { - try { - $store = Store::findOrFail($store); - } catch (\Throwable $th) { - return false; - } - } - - - try { - $product->stores()->syncWithoutDetaching([ - $store->id => [ - 'available' => $available ?? 0, - 'price' => $price ?? 0, - 'is_active' => $is_active, - ] - ]); - return true; - } catch (\Throwable $th) { - return false; - } - } - - /** - * Attach a product to a store with optional pivot data. - * - * This creates (or updates) a relationship between a given Store and Product - * in the `product_store` pivot table. You can specify availability, price, - * and activation status as pivot attributes. - * - * If either the store or product is not found, the method safely returns false. - * - * @param \App\Models\Market\Store|int $store The Store model instance or its ID. - * @param \App\Models\Market\Product|int $product The Product model instance or its ID. - * @param int $available Quantity available (default: 0) - * @param int $price Product price (default: 0) - * @param bool $is_active Whether the product is active in this store (default: true) - * @return bool Returns true on success, false if store/product not found or an exception occurs. - * - * @example - * ```php - * // Attach product #10 to store #3 - * Store::attachProductToStore(3, 10, 25, 1999, true); - * - * // Using model instances - * $store = Store::find(3); - * $product = Product::find(10); - * Store::attachProductToStore($store, $product, available: 100, price: 2499); - * ``` - */ - public static function attachProducttoStore(Store|int $store, Product|int $product, int $available = 0, int $price = 0, bool $is_active = true) - { - if (is_int($store)) { - try { - $store = Store::findOrFail($store); - } catch (\Throwable $th) { - return false; - } - } - if (is_int($product)) { - try { - $product = Product::findOrFail($product); - } catch (\Throwable $th) { - return false; - } - } - - $store->products()->attach($product->id, ['available' => $available ?? 0, 'price' => $price ?? 0, 'is_active' => $is_active,]); - return true; - - } - - - /** - * Detach (unlink) a product from a store. - * - * This removes the relationship between a given Store and Product - * from the `product_store` pivot table, without deleting either record. - * - * If the link does not exist, this method silently succeeds (no error is thrown). - * If either the store or product cannot be found, the method safely returns false. - * - * @param \App\Models\Market\Store|int $store The Store model instance or its ID. - * @param \App\Models\Market\Product|int $product The Product model instance or its ID. - * @return bool Returns true on success, false if store/product not found or an exception occurs. - * - * @example - * ```php - * // Detach product #10 from store #3 - * Store::detachProductFromStore(3, 10); - * - * // Using model instances - * $store = Store::find(3); - * $product = Product::find(10); - * Store::detachProductFromStore($store, $product); - * ``` - */ - public static function detachProductFromStore(Store|int $store, Product|int $product) - { - // Resolve Store - if (is_int($store)) { - try { - $store = Store::findOrFail($store); - } catch (\Throwable $th) { - return false; - } - } - - // Resolve Product - if (is_int($product)) { - try { - $product = Product::findOrFail($product); - } catch (\Throwable $th) { - return false; - } - } - - // Attempt to detach - try { - $store->products()->detach($product->id); - return true; - } catch (\Throwable $th) { - return false; - } - } - - - - - /** - * Detach (unlink) a store from a product. - * - * This removes the relationship between a given Product and Store - * from the `product_store` pivot table, without deleting either record. - * - * If the relationship doesn’t exist, the operation succeeds silently. - * If either the product or store is not found, the method safely returns false. - * - * @param \App\Models\Market\Product|int $product The Product model instance or its ID. - * @param \App\Models\Market\Store|int $store The Store model instance or its ID. - * @return bool Returns true on success, false if product/store not found or an exception occurs. - * - * @example - * ```php - * // Detach store #2 from product #7 - * Product::detachStoreFromProduct(7, 2); - * - * // Using model instances - * $product = Product::find(7); - * $store = Store::find(2); - * Product::detachStoreFromProduct($product, $store); - * ``` - */ - public static function detachStoreFromProduct(Product|int|string $product, Store|int|string $store) - { - if (is_int($product)) { - try { - $product = Product::findOrFail($product); - } catch (\Throwable $th) { - $product = null; - } - } - - if (is_int($store)) { - try { - $store = Store::findOrFail($store); - } catch (\Throwable $th) { - $store = null; - } - } - - if (is_string($product)) { - try { - $product = Product::where('hashkey', $product)->first(); - } catch (\Throwable $th) { - $store = null; - } - } - - if (is_string($store)) { - try { - $store = Store::where('hashkey', $store)->first(); - } catch (\Throwable $th) { - $store = null; - } - } - - if (!$store || !$product) { - return false; - } - - // Attempt to detach - try { - $product->stores()->detach($store->id); - return true; - } catch (\Throwable $th) { - return false; - } - } - - - - /** - * Synchronize (replace) all products attached to a given store. - * - * This method accepts either a Store model instance or a store ID. - * It replaces the store's entire product list in the `product_store` pivot table - * with the provided data array. - * - * Existing product links not included in the `$products` array will be detached. - * New links will be created, and existing ones will be updated. - * - * @param \App\Models\Market\Store|int $store The Store model instance or its ID. - * @param array $products Associative array of product IDs and pivot attributes. - * Format: - * [ - * product_id => [ - * 'available' => (int), - * 'price' => (int), - * 'is_active' => (bool), - * ], - * ... - * ] - * @return bool Returns true on success, false if the store is not found or an exception occurs. - * - * @example - * ```php - * // Replace all products for store #1 - * Store::syncProductsToStore(1, [ - * 10 => ['available' => 50, 'price' => 1499, 'is_active' => true], - * 11 => ['available' => 20, 'price' => 1999, 'is_active' => false], - * ]); - * - * // Using a Store instance - * $store = Store::find(1); - * Store::syncProductsToStore($store, [ - * 5 => ['available' => 100, 'price' => 2999, 'is_active' => true], - * ]); - * ``` - */ - public static function syncProductsToStore(Store|int $store, array $products) - { - // $products format: [product_id => ['available' => ..., 'price' => ..., 'is_active' => ...]] - - // Resolve store - if (is_int($store)) { - try { - $store = Store::findOrFail($store); - } catch (\Throwable $th) { - return false; - } - } - - // Validate array structure (optional) - if (!is_array($products) || empty($products)) { - return false; - } - - try { - // Replace all existing product links - $store->products()->sync($products); - return true; - } catch (\Throwable $th) { - return false; - } - } - - - - /** - * Synchronize (replace) all stores attached to a given product. - * - * This method accepts either a Product model instance or a product ID. - * It replaces the product's entire store list in the `product_store` pivot table - * with the provided data array. - * - * Existing store links not listed in the `$stores` array will be detached. - * New ones will be created, and existing ones will be updated. - * - * @param \App\Models\Market\Product|int $product The Product model instance or its ID. - * @param array $stores Associative array of store IDs and pivot attributes. - * Format: - * [ - * store_id => [ - * 'available' => (int), - * 'price' => (int), - * 'is_active' => (bool), - * ], - * ... - * ] - * @return bool Returns true on success, false if the product is not found or an exception occurs. - * - * @example - * ```php - * // Replace all stores linked to product #5 - * Product::syncStoresToProduct(5, [ - * 1 => ['available' => 30, 'price' => 1799, 'is_active' => true], - * 2 => ['available' => 15, 'price' => 1999, 'is_active' => false], - * ]); - * - * // Using a Product instance - * $product = Product::find(5); - * Product::syncStoresToProduct($product, [ - * 3 => ['available' => 40, 'price' => 1499, 'is_active' => true], - * ]); - * ``` - */ - public static function syncStoresToProduct(Product|int $product, array $stores) - { - // $stores format: [store_id => ['available' => ..., 'price' => ..., 'is_active' => ...]] - - // Resolve product - if (is_int($product)) { - try { - $product = Product::findOrFail($product); - } catch (\Throwable $th) { - return false; - } - } - - if (!is_array($stores) || empty($stores)) { - return false; - } - - try { - // Replace all existing store links - $product->stores()->sync($stores); - return true; - } catch (\Throwable $th) { - return false; - } - } - - public function listStoresActiveDataAll() - { - $stores = Store::where('status', 'active') - ->select('hashkey', 'photourl', 'name', 'category', 'subcategory') - ->get() - ->map(function ($store) { - if (is_array($store->photourl)) { - $photoUrl = $store->photourl; - } elseif (is_string($store->photourl)) { - $photoUrl = json_decode($store->photourl, true) ?: []; - } else { - $photoUrl = []; - } - - if (is_array($photoUrl) && !empty($photoUrl)) { - $photoUrl = $photoUrl[0]; - } else { - $photoUrl = null; - } - - - $store->photourl = $photoUrl; - - return $store; - }); - - return $stores; - } - - /** - * List stores the current user is authorized to manage. - * - Ultimate users: all stores - * - Store owners: stores they own - * - Store managers: stores they manage - * Returns a JSON array of stores with hashkey, name, and role. - */ - public function listStoresForCurrentUser() - { - $user = Auth::user(); - - if (!$user) { - return Response::json([]); - } - - $isUltimate = $user->acct_type === UserTypes::ULTIMATE; - $isSuperOperator = $user->acct_type === UserTypes::SUPER_OPERATOR; - $isOperator = $user->acct_type === UserTypes::OPERATOR; - - if ($isUltimate) { - // Ultimate can see all active stores - $stores = Store::where('is_active', true) - ->select(['hashkey', 'name', 'category', 'owner_id', 'manager_id']) - ->get(); - } elseif ($isSuperOperator || $isOperator) { - // Super Operator and Operator see their own and descendants' stores - $descendants = $user->getAllDescendants(); - $allowedIds = $descendants->pluck('id')->push($user->id)->toArray(); - - $stores = Store::where('is_active', true) - ->where(function($query) use ($allowedIds) { - $query->whereIn('owner_id', $allowedIds) - ->orWhereIn('manager_id', $allowedIds); - }) - ->select(['hashkey', 'name', 'category', 'owner_id', 'manager_id']) - ->get(); - } else { - // Non-admin users: only stores they own or manage (including store_managers pivot) - $stores = Store::where('is_active', true) - ->where(function($query) use ($user) { - $query->where('owner_id', $user->id) - ->orWhere('manager_id', $user->id) - ->orWhereHas('managers', function ($mq) use ($user) { - $mq->where('user_id', $user->id); - }); - }) - ->select(['hashkey', 'name', 'category', 'owner_id', 'manager_id']) - ->get(); - } - - $formattedStores = $stores->map(function ($store) use ($user) { - $role = 'viewer'; - if ($store->owner_id === $user->id) { - $role = 'owner'; - } elseif ($store->manager_id === $user->id) { - $role = 'manager'; - } elseif ($store->managers()->where('user_id', $user->id)->exists()) { - $role = 'manager'; - } elseif ($user->isUltimate() || $user->isSuperOperator() || $user->isOperator()) { - $role = 'admin'; - } - return [ - 'hashkey' => $store->hashkey, - 'name' => $store->name, - 'category' => $store->category, - 'role' => $role, - ]; - }); - - return Response::json($formattedStores); - } - - public function removeProductfromStore() - { - //TODO fix permissions here - - $product = request()->input('product_hash'); - $store = request()->input('store_hash'); - - - - $isUlt = Auth::user()->acct_type === UserTypes::ULTIMATE; - - if ($isUlt) { - $allowed = ProductPermissions::isActionAllowed(UserActions::AddProducttoAnyStore, $product, $store); - } else { - $allowed = ProductPermissions::isActionAllowed(UserActions::AddProducttoOwnStore, $product, $store); - } - - - if (!$allowed) { - return ResponseHelper::returnUnauthorized(); - } - - - - $go = self::detachStoreFromProduct($product, $store); - - if ($go === true) { - $datareturn = [ - 'store_hash' => $store, - 'product_hash' => $product, - ]; - return ResponseHelper::returnSuccessResponse($datareturn, $product, ''); - } else { - return ResponseHelper::returnError($go); - } - } - - - public function listSelectableStoresForAddingProduct() - { - /** @var \App\Models\User $user */ - $user = Auth::user(); - if (!$user) { - return ResponseHelper::returnUnauthorized(); - } - - $acctType = $user->acct_type instanceof UserTypes ? $user->acct_type : UserTypes::tryFrom($user->acct_type); - $isBig3 = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR]); - - if ($isBig3) { - // Big 3 can see all stores - $stores = Store::select(['hashkey', 'name', 'category', 'owner_id', 'manager_id']) - ->get(); - } else { - // Include self + all descendants so a store owner sees their own stores. - $allowedUserIds = array_merge([$user->id], $user->getAllDescendants()->pluck('id')->toArray()); - - $stores = Store::where(function ($q) use ($allowedUserIds) { - $q->whereIn('owner_id', $allowedUserIds) - ->orWhereIn('manager_id', $allowedUserIds) - ->orWhereHas('managers', function ($mq) use ($allowedUserIds) { - $mq->whereIn('user_id', $allowedUserIds); - }); - }) - ->select(['hashkey', 'name', 'category', 'owner_id', 'manager_id']) - ->get(); - } - - $formattedStores = $stores->map(function ($store) use ($user) { - $role = 'admin'; - if ($store->owner_id === $user->id) { - $role = 'owner'; - } elseif ($store->manager_id === $user->id) { - $role = 'manager'; - } - return [ - 'hashkey' => $store->hashkey, - 'name' => $store->name, - 'category' => $store->category, - 'role' => $role, - ]; - }); - - return response()->json([ - 'success' => true, - 'data' => $formattedStores, - ]); - } - - public function listStores_Admin(Request $request) - { - try { - $user = Auth::user(); - if (!$user) { - return ResponseHelper::returnUnauthorized(); - } - - $acctType = $user->acct_type instanceof UserTypes ? $user->acct_type : UserTypes::tryFrom($user->acct_type); - $isBig3 = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR]); - - $columns = ['id', 'hashkey', 'name', 'category', 'subcategory', 'is_active', 'status', 'photourl', 'address', 'owner_id', 'manager_id']; - - if ($isBig3) { - $stores = Store::with(['cooperatives:organizations.id,organizations.hashkey,organizations.name']) - ->select($columns) - ->orderBy('id', 'desc') - ->get() - ->each(fn($s) => $s->setAttribute('user_can_manage', true)); - } else { - $allowedUserIds = array_merge([$user->id], $user->getAllDescendants()->pluck('id')->toArray()); - - $stores = Store::with(['cooperatives:organizations.id,organizations.hashkey,organizations.name', 'managers:id,store_id,user_id']) - ->select($columns) - ->where(function ($q) use ($allowedUserIds) { - $q->whereIn('owner_id', $allowedUserIds) - ->orWhereIn('manager_id', $allowedUserIds) - ->orWhereHas('managers', function ($mq) use ($allowedUserIds) { - $mq->whereIn('user_id', $allowedUserIds); - }); - }) - ->orderBy('id', 'desc') - ->get() - ->each(function ($s) use ($allowedUserIds) { - $s->setAttribute('user_can_manage', - in_array($s->owner_id, $allowedUserIds) - || in_array($s->manager_id, $allowedUserIds) - || $s->managers->pluck('user_id')->intersect($allowedUserIds)->isNotEmpty() - ); - unset($s->managers); - }); - } - - return response()->json([ - 'success' => true, - 'stores' => $stores - ]); - } catch (\Throwable $e) { - \Hypervel\Support\Facades\Log::error('listStores_Admin failed', [ - 'user_id' => Auth::id(), - 'message' => $e->getMessage(), - 'file' => $e->getFile() . ':' . $e->getLine(), - ]); - return response()->json([ - 'success' => false, - 'message' => 'Failed to load stores: ' . $e->getMessage(), - ], 500); - } - } - - /** - * Resolve cooperative hashkeys to ids (filtering to type=COOPERATIVE) - * and sync onto the store. When $forceReplace is false and the input - * array is empty, this is a no-op (used on create). - */ - private function syncStoreCooperatives(Store $store, $coopHashes, bool $forceReplace = false): void - { - if (!is_array($coopHashes)) { - $coopHashes = []; - } - - if (!$forceReplace && empty($coopHashes)) { - return; - } - - $coopIds = empty($coopHashes) - ? [] - : Organization::whereIn('hashkey', $coopHashes) - ->where('type', 'COOPERATIVE') - ->pluck('id') - ->toArray(); - - $store->cooperatives()->sync($coopIds); - } - - /** - * Lightweight cooperative list for the store create/edit pickers. - * Returns only active organizations of type COOPERATIVE. - */ - public function listCooperativesForStore(Request $request) - { - $user = Auth::user(); - if (!$user) { - return ResponseHelper::returnUnauthorized(); - } - - $coops = Organization::where('is_active', true) - ->where('type', 'COOPERATIVE') - ->select(['hashkey', 'name', 'cooperative_type', 'address']) - ->orderBy('name') - ->get(); - - return response()->json([ - 'success' => true, - 'data' => $coops, - ]); - } - - public function deleteStore_Admin(Request $request) - { - $user = Auth::user(); - if (!$user) { - return ResponseHelper::returnUnauthorized(); - } - - $hash = $request->input('target'); - if (!$hash) { - return ResponseHelper::returnIncorrectDetails(); - } - - try { - $store = Store::where('hashkey', $hash)->firstOrFail(); - - // Check permissions: Ultimate or Owner/Manager (or descendant thereof, or pivot manager) - $isUltimate = $user->acct_type === UserTypes::ULTIMATE; - if (!$isUltimate) { - $descendants = $user->getAllDescendants(); - $descendantIds = $descendants->pluck('id')->toArray(); - $allowedIds = array_merge([$user->id], $descendantIds); - $isManagerViaPivot = $store->managers()->whereIn('user_id', $allowedIds)->exists(); - if (!in_array($store->owner_id, $allowedIds) && !in_array($store->manager_id, $allowedIds) && !$isManagerViaPivot) { - return ResponseHelper::returnUnauthorized(); - } - } - - $store->delete(); - return ResponseHelper::returnSuccessResponse([], $hash, 'Store deleted successfully'); - } catch (\Exception $e) { - return ResponseHelper::returnError('Failed to delete store: ' . $e->getMessage()); - } - } - - public function toggleStoreStatus_Admin(Request $request) - { - $user = Auth::user(); - if (!$user) { - return ResponseHelper::returnUnauthorized(); - } - - $hash = $request->input('target'); - if (!$hash) { - return ResponseHelper::returnIncorrectDetails(); - } - - try { - $store = Store::where('hashkey', $hash)->firstOrFail(); - - // Check permissions: Ultimate or Owner/Manager (or descendant thereof, or pivot manager) - $isUltimate = $user->acct_type === UserTypes::ULTIMATE; - if (!$isUltimate) { - $descendants = $user->getAllDescendants(); - $descendantIds = $descendants->pluck('id')->toArray(); - $allowedIds = array_merge([$user->id], $descendantIds); - $isManagerViaPivot = $store->managers()->whereIn('user_id', $allowedIds)->exists(); - if (!in_array($store->owner_id, $allowedIds) && !in_array($store->manager_id, $allowedIds) && !$isManagerViaPivot) { - return ResponseHelper::returnUnauthorized(); - } - } - - $store->is_active = !$store->is_active; - $store->save(); - - return ResponseHelper::returnSuccessResponse( - ['is_active' => $store->is_active], - $hash, - 'Store status updated successfully' - ); - } catch (\Exception $e) { - return ResponseHelper::returnError('Failed to update store status: ' . $e->getMessage()); - } - } -} \ No newline at end of file diff --git a/app/Http/Controllers/Market/UltimateController.php b/app/Http/Controllers/Market/UltimateController.php deleted file mode 100644 index 064d446..0000000 --- a/app/Http/Controllers/Market/UltimateController.php +++ /dev/null @@ -1,565 +0,0 @@ -checkAccess()) return ResponseHelper::returnUnauthorized(); - - $globalMessage = Redis::get('system:global_message'); - - $redisStatus = ['connected' => false, 'ping_ms' => null, 'used_memory_human' => null, 'version' => null, 'error' => null]; - try { - $start = microtime(true); - $pong = Redis::ping(); - $redisStatus['ping_ms'] = round((microtime(true) - $start) * 1000, 2); - $redisStatus['connected'] = $pong === true || $pong === 'PONG' || $pong === '+PONG' || (is_string($pong) && stripos($pong, 'PONG') !== false); - - $info = Redis::info(); - if (is_array($info)) { - $flat = isset($info['Memory']) && is_array($info['Memory']) ? $info['Memory'] : $info; - $redisStatus['used_memory_human'] = $flat['used_memory_human'] ?? null; - $serverInfo = isset($info['Server']) && is_array($info['Server']) ? $info['Server'] : $info; - $redisStatus['version'] = $serverInfo['redis_version'] ?? null; - } - } catch (\Throwable $e) { - $redisStatus['error'] = $e->getMessage(); - } - - $stats = [ - 'users' => User::count(), - 'active_users' => User::where('active', true)->count(), - 'stores' => Store::count(), - 'active_stores' => Store::where('is_active', true)->count(), - 'products' => Product::count(), - 'transactions' => GlobalTransaction::count(), - 'total_balance' => GlobalTransaction::sum('amount'), - 'php_version' => PHP_VERSION, - 'server_time' => date('Y-m-d H:i:s'), - 'maintenance_mode' => Redis::get('system:maintenance_mode') === 'true', - 'global_message' => $globalMessage ? json_decode($globalMessage, true) : null, - 'logs_count' => DB::table('logs')->count(), - 'table_logs_count' => DB::table('table_logs')->count(), - 'pos_sessions_count' => DB::table('pos_sessions')->count(), - 'cooperatives_count' => DB::table('organizations')->where('type', 'COOPERATIVE')->count(), - 'carts_count' => DB::table('carts')->count(), - 'farmer_profiles_count' => DB::table('farmer_profiles')->count(), - 'redis' => $redisStatus, - ]; - - return Response::json(['success' => true, 'data' => $stats]); - } - - /** - * Execute a raw SQL query. - */ - public function runQuery(Request $request) - { - if (Auth::user()->acct_type !== \App\Enums\UserTypes::ULTIMATE || !UserPermissions::isActionPermitted(0, UserActions::UltimateQuery)) { - return ResponseHelper::returnUnauthorized(); - } - - $query = $request->input('query'); - if (empty($query)) return ResponseHelper::returnError('Query cannot be empty'); - - try { - $queryLower = strtolower(trim($query)); - if (str_starts_with($queryLower, 'select') || str_starts_with($queryLower, 'show') || str_starts_with($queryLower, 'describe')) { - $results = DB::select($query); - return Response::json(['success' => true, 'data' => $results]); - } else { - $affected = DB::statement($query); - return Response::json(['success' => true, 'affected' => $affected]); - } - } catch (\Throwable $th) { - return ResponseHelper::returnError($th->getMessage()); - } - } - - /** - * Toggle maintenance mode system-wide. - */ - public function toggleMaintenance(Request $request) - { - if (!$this->checkAccess() || !UserPermissions::isActionPermitted(0, UserActions::UltimateMaintenance)) { - return ResponseHelper::returnUnauthorized(); - } - - $enabled = (bool) $request->input('enabled'); - Redis::set('system:maintenance_mode', $enabled ? 'true' : 'false'); - - return Response::json(['success' => true, 'maintenance_mode' => $enabled]); - } - - /** - * Send a global message / broadcast. - */ - public function sendGlobalMessage(Request $request) - { - if (!$this->checkAccess() || !UserPermissions::isActionPermitted(0, UserActions::UltimateGlobalMessage)) { - return ResponseHelper::returnUnauthorized(); - } - - $message = $request->input('message'); - $type = $request->input('type', 'info'); // info, success, warning, danger - - if (empty($message)) { - Redis::del('system:global_message'); - return Response::json(['success' => true, 'message' => 'Global message cleared']); - } - - Redis::set('system:global_message', json_encode([ - 'text' => $message, - 'type' => $type, - 'timestamp' => time() - ])); - - return Response::json(['success' => true]); - } - - /** - * Flush / Truncate specific tables. - */ - public function flushData(Request $request) - { - if (!$this->checkAccess() || !UserPermissions::isActionPermitted(0, UserActions::UltimateFlush)) { - return ResponseHelper::returnUnauthorized(); - } - - $target = $request->input('target'); - - try { - $affected = 0; - switch ($target) { - case 'transactions': - $affected = GlobalTransaction::count(); - DB::statement('SET FOREIGN_KEY_CHECKS = 0'); - GlobalTransaction::truncate(); - DB::statement('SET FOREIGN_KEY_CHECKS = 1'); - break; - case 'pos_sessions': - $affected = DB::table('pos_sessions')->count(); - DB::table('pos_sessions')->truncate(); - break; - case 'cache': - Redis::flushDB(); - break; - case 'stores': - $affected = DB::table('str')->count(); - DB::statement('SET FOREIGN_KEY_CHECKS = 0'); - DB::table('prd_str')->truncate(); - DB::table('store_managers')->truncate(); - DB::table('str')->truncate(); - DB::statement('SET FOREIGN_KEY_CHECKS = 1'); - break; - case 'products': - $affected = DB::table('prd_items')->count(); - DB::statement('SET FOREIGN_KEY_CHECKS = 0'); - DB::table('prd_str')->truncate(); - DB::table('prd_items')->truncate(); - DB::statement('SET FOREIGN_KEY_CHECKS = 1'); - break; - case 'cooperatives': - $affected = DB::table('organizations')->where('type', 'COOPERATIVE')->count(); - DB::statement('SET FOREIGN_KEY_CHECKS = 0'); - DB::table('cooperative_votes')->truncate(); - DB::table('cooperative_resolutions')->truncate(); - DB::table('cooperative_documents')->truncate(); - DB::table('cooperative_members')->truncate(); - DB::table('organizations')->where('type', 'COOPERATIVE')->delete(); - DB::statement('SET FOREIGN_KEY_CHECKS = 1'); - break; - case 'carts': - $affected = DB::table('carts')->count(); - DB::statement('SET FOREIGN_KEY_CHECKS = 0'); - DB::table('cart_items')->truncate(); - DB::table('carts')->truncate(); - DB::statement('SET FOREIGN_KEY_CHECKS = 1'); - break; - case 'farmer_profiles': - $affected = DB::table('farmer_profiles')->count(); - DB::statement('SET FOREIGN_KEY_CHECKS = 0'); - DB::table('farmer_profiles')->truncate(); - DB::statement('SET FOREIGN_KEY_CHECKS = 1'); - break; - default: - return ResponseHelper::returnError('Invalid flush target'); - } - return Response::json(['success' => true, 'message' => "Flushed $target successfully", 'affected' => $affected]); - } catch (\Throwable $th) { - return ResponseHelper::returnError($th->getMessage()); - } - } - - /** - * Trigger a test notification for a specific user. - */ - public function testNotification(Request $request) - { - if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); - - $userHash = $request->input('user_hash'); - $user = User::where('hashkey', $userHash)->first(); - if (!$user) return ResponseHelper::returnError('User not found'); - - // Setting exec_command which SSEController picks up to notify client - $user->exec_command = 'toast:success:Test Notification from Ultimate Console: ' . date('H:i:s'); - $user->save(); - - return Response::json(['success' => true]); - } - - /** - * Batch management for various entities. - */ - public function batchManage(Request $request) - { - if (!$this->checkAccess() || !UserPermissions::isActionPermitted(0, UserActions::UltimateBatch)) { - return ResponseHelper::returnUnauthorized(); - } - - $action = $request->input('action'); - $ids = $request->input('ids', []); - $data = $request->input('data', []); - - if (empty($ids) && !in_array($action, ['cleanup_sessions'])) { - return ResponseHelper::returnError('No IDs provided'); - } - - try { - switch($action) { - case 'activate_users': - User::whereIn('id', $ids)->update(['active' => true]); - break; - case 'deactivate_users': - User::whereIn('id', $ids)->update(['active' => false]); - break; - case 'cleanup_sessions': - DB::table('pos_sessions')->where('status', 'VOIDED')->delete(); - break; - case 'mass_transfer_points': - $amount = (float)($data['amount'] ?? 0); - if ($amount <= 0) return ResponseHelper::returnError('Invalid amount'); - - foreach ($ids as $id) { - GlobalTransaction::create([ - 'user_id' => $id, - 'amount' => $amount, - 'type' => 'REWARD', - 'description' => 'Mass points adjustment via Ultimate Console', - 'is_active' => true, - ]); - } - break; - default: - return ResponseHelper::returnError('Invalid batch action'); - } - return Response::json(['success' => true]); - } catch (\Throwable $th) { - return ResponseHelper::returnError($th->getMessage()); - } - } - - /** - * Run a system command (Artisan wrapper). - */ - public function runCommand(Request $request) - { - if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); - - $command = $request->input('command'); - if (empty($command)) return ResponseHelper::returnError('Command cannot be empty'); - - // Normalize command: strip 'php artisan ' if present - $command = preg_replace('/^php artisan\s+/', '', trim($command)); - - // Mapping for user-friendly commands - if ($command === 'reset-app all users') { - $command = 'app:reset-users'; - } - - if ($command === 'db seed') { - $command = 'db:seed'; - } - - // For security, only allow specific commands - $allowedCommands = [ - 'cache:clear', 'view:clear', 'config:clear', 'route:clear', - 'migrate', 'migrate:rollback', 'migrate:fresh', - 'db:seed', 'app:reset-users', 'optimize', 'optimize:clear' - ]; - - $baseCommand = explode(' ', trim($command))[0]; - - if (!in_array($baseCommand, $allowedCommands)) { - return ResponseHelper::returnError("Command '{$baseCommand}' not allowed for security reasons."); - } - - try { - // In Hyperf, running commands from HTTP request context is tricky. - // We'll use shell_exec in this local environment demo as a fallback. - $output = shell_exec("php artisan $command 2>&1"); - return Response::json(['success' => true, 'output' => $output]); - } catch (\Throwable $th) { - return ResponseHelper::returnError($th->getMessage()); - } - } - - /** - * Run `php artisan migrate --force` (non-interactive). - */ - public function runMigrate(Request $request) - { - if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); - - try { - $output = shell_exec('cd ' . escapeshellarg(BASE_PATH) . ' && php artisan migrate --force 2>&1'); - return Response::json(['success' => true, 'output' => $output]); - } catch (\Throwable $th) { - return ResponseHelper::returnError($th->getMessage()); - } - } - - /** - * Download a full database backup. - * Puts system in maintenance mode during the process. - */ - public function downloadBackup() - { - if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); - - // 1. Enable maintenance mode & Notify - Redis::set('system:maintenance_mode', 'true'); - Redis::set('system:global_message', json_encode(['text' => 'System backup in progress. Transactions temporarily disabled.', 'type' => 'warning'])); - - try { - $filename = 'backup_' . date('Y-m-d_H-i-s') . '.sql'; - $path = BASE_PATH . '/storage/app/backups/' . $filename; - - if (!is_dir(dirname($path))) { - mkdir(dirname($path), 0755, true); - } - - $dbHost = env('DB_HOST', '127.0.0.1'); - $dbPort = env('DB_PORT', '3306'); - $dbName = env('DB_DATABASE', 'bukid'); - $dbUser = env('DB_USERNAME', 'root'); - $dbPass = env('DB_PASSWORD', ''); - - $dump = new \Ifsnop\Mysqldump\Mysqldump( - "mysql:host={$dbHost};port={$dbPort};dbname={$dbName}", - $dbUser, - $dbPass, - [ - 'add-drop-table' => true, - 'exclude-tables' => ['db_backups'] // Exclude the backups table - ] - ); - - $dump->start($path); - - if (!file_exists($path) || filesize($path) === 0) { - throw new \Exception('Backup file was not created or is empty.'); - } - - // Compress into 7z Ultra - $sevenZFilename = 'backup_' . date('Y-m-d_H-i-s') . '.7z'; - $sevenZPath = BASE_PATH . '/storage/app/backups/' . $sevenZFilename; - - // -mx=9 for Ultra compression - $path_escaped = escapeshellarg($path); - $sevenZPath_escaped = escapeshellarg($sevenZPath); - $command = "7z a -t7z -m0=lzma2 -mx=9 {$sevenZPath_escaped} {$path_escaped} 2>&1"; - shell_exec($command); - - if (!file_exists($sevenZPath)) { - throw new \Exception('Failed to create 7z archive.'); - } - - // Save to database - $fileContentRaw = file_get_contents($sevenZPath); - $fileHash = hash('sha256', $fileContentRaw); - - $fileContent = new FileContent(); - $fileContent->filehash = $fileHash; - $fileContent->titlename = $sevenZFilename; - $fileContent->description = 'System database backup'; - $fileContent->size_in_bytes = filesize($sevenZPath); - $fileContent->content = base64_encode($fileContentRaw); - $fileContent->mimetype = 'application/x-7z-compressed'; - $fileContent->created_by = Auth::id(); - $fileContent->updated_by = Auth::id(); - $fileContent->save(); - - $dbBackup = new DbBackup(); - $dbBackup->file_content_hashkey = $fileContent->hashkey; - $dbBackup->filename = $sevenZFilename; - $dbBackup->size_in_bytes = filesize($sevenZPath); - $dbBackup->created_by = Auth::id(); - $dbBackup->updated_by = Auth::id(); - $dbBackup->save(); - - // Clean up the temporary files from filesystem - @unlink($path); - @unlink($sevenZPath); - - // 2. Disable maintenance mode & Clear Notify - Redis::set('system:maintenance_mode', 'false'); - Redis::del('system:global_message'); - - // 3. Return the binary content for download - return Response::make($fileContentRaw, 200, [ - 'Content-Type' => 'application/x-7z-compressed', - 'Content-Disposition' => 'attachment; filename="' . $sevenZFilename . '"', - 'Content-Length' => strlen($fileContentRaw), - ]); - } catch (\Throwable $th) { - Redis::set('system:maintenance_mode', 'false'); - return ResponseHelper::returnError($th->getMessage()); - } - } - - /** - * Get recently created backups. - */ - public function getBackups() - { - if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); - - $backups = DbBackup::with(['creator']) - ->orderBy('created_at', 'desc') - ->limit(50) - ->get(); - - return Response::json(['success' => true, 'data' => $backups]); - } - - /** - * Rename a specific backup. - */ - public function renameBackup(Request $request) - { - if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); - - $hash = $request->input('hash'); - $newName = $request->input('name'); - - if (empty($newName)) return ResponseHelper::returnError('Name cannot be empty'); - - $backup = DbBackup::where('hashkey', $hash)->first(); - if (!$backup) return ResponseHelper::returnError('Backup not found'); - - $backup->name = $newName; - $backup->save(); - - return Response::json(['success' => true]); - } - - /** - * Delete a specific backup. - */ - public function deleteBackup(Request $request) - { - if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); - - $hash = $request->input('hash'); - $backup = DbBackup::where('hashkey', $hash)->first(); - if (!$backup) return ResponseHelper::returnError('Backup not found'); - - // Delete associated file content - FileContent::where('hashkey', $backup->file_content_hashkey)->delete(); - - // Delete backup record - $backup->delete(); - - return Response::json(['success' => true]); - } - - /** - * Download a specific backup from the database. - */ - public function downloadBackupByHash(Request $request) - { - if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); - - $hash = $request->input('hash'); - $backup = DbBackup::where('hashkey', $hash)->first(); - if (!$backup) return ResponseHelper::returnError('Backup not found'); - - $fileContent = FileContent::where('hashkey', $backup->file_content_hashkey)->first(); - if (!$fileContent) return ResponseHelper::returnError('File content not found'); - - $content = base64_decode($fileContent->content); - - return Response::make($content, 200, [ - 'Content-Type' => $fileContent->mimetype, - 'Content-Disposition' => 'attachment; filename="' . $backup->filename . '"', - 'Content-Length' => strlen($content), - ]); - } - - - /** - * Get system-wide logs from file and database. - */ - public function getSystemLogs(Request $request) - { - if (!$this->checkAccess()) return ResponseHelper::returnUnauthorized(); - - $type = $request->input('type', 'database'); - - if ($type === 'file') { - $logPath = BASE_PATH . '/storage/logs/hypervel.log'; - if (!file_exists($logPath)) { - return Response::json(['success' => true, 'data' => 'No file logs found.']); - } - $logs = shell_exec("tail -n 1000 " . escapeshellarg($logPath)); - return Response::json(['success' => true, 'data' => $logs]); - } - - // Database logs (audit) - if ($type === 'audit') { - $logs = DB::table('table_logs')->orderBy('id', 'desc')->limit(500)->get(); - return Response::json(['success' => true, 'data' => $logs]); - } - - // Database logs (system) - $logs = DB::table('logs')->orderBy('uid', 'desc')->limit(500)->get(); - return Response::json(['success' => true, 'data' => $logs]); - } -} diff --git a/app/Http/Controllers/Market/UserInfoController.php b/app/Http/Controllers/Market/UserInfoController.php deleted file mode 100644 index 000ceec..0000000 --- a/app/Http/Controllers/Market/UserInfoController.php +++ /dev/null @@ -1,169 +0,0 @@ -first(); - if (!$targetUser) { - return ResponseHelper::returnError('User not found', 404); - } - - $currentUser = Auth::user(); - if (!$currentUser) { - return ResponseHelper::returnUnauthorized(); - } - - // Check permission: can view self or has ViewUserInfo permission for others - if ($currentUser->id !== $targetUser->id && !UserPermissions::isActionPermitted($currentUser->acct_type, UserActions::ViewUserInfo)) { - return ResponseHelper::returnUnauthorized(); - } - - $userInfo = $targetUser->userInfo; - if (!$userInfo) { - // Lazy create if it doesn't exist (should have been backfilled but just in case) - $userInfo = UserInfo::create([ - 'user_id' => $targetUser->id, - 'fullname' => $targetUser->fullname ?? $targetUser->name, - 'email' => $targetUser->email, - 'mobile' => $targetUser->mobile_number, - 'is_active' => true, - ]); - } - - return response()->json([ - 'success' => true, - 'data' => $userInfo - ]); - } - - public function updateUserInfo(Request $request, string $hashkey) - { - $targetUser = User::where('hashkey', $hashkey)->first(); - if (!$targetUser) { - return ResponseHelper::returnError('User not found', 404); - } - - $currentUser = Auth::user(); - if (!$currentUser) { - return ResponseHelper::returnUnauthorized(); - } - - // Check permission: can manage self or has ManageUserInfo permission for others - if ($currentUser->id !== $targetUser->id && !UserPermissions::isActionPermitted($currentUser->acct_type, UserActions::ManageUserInfo)) { - return ResponseHelper::returnUnauthorized(); - } - - $userInfo = $targetUser->userInfo; - if (!$userInfo) { - $userInfo = new UserInfo(['user_id' => $targetUser->id]); - } - - $validated = $request->validate([ - 'firstname' => 'nullable|string|max:255', - 'middlename' => 'nullable|string|max:255', - 'lastname' => 'nullable|string|max:255', - 'suffix' => 'nullable|string|max:50', - 'gender' => 'nullable|string|max:50', - 'dob' => 'nullable|date', - 'priority_sector' => 'nullable|string|max:255', - 'messenger_id' => 'nullable|string|max:255', - 'viber_number' => 'nullable|string|max:255', - 'tiktok_username' => 'nullable|string|max:255', - 'region' => 'nullable|string|max:255', - 'province' => 'nullable|string|max:255', - 'city' => 'nullable|string|max:255', - 'barangay' => 'nullable|string|max:255', - 'civil_status' => 'nullable|string|max:100', - 'children_count' => 'nullable|integer', - 'dependent_count' => 'nullable|integer', - 'education_level' => 'nullable|string|max:255', - 'course' => 'nullable|string|max:255', - 'school' => 'nullable|string|max:255', - 'year_last_attended' => 'nullable|string|max:50', - 'livelihood_source' => 'nullable|string|max:255', - 'last_company' => 'nullable|string|max:255', - 'employer_name' => 'nullable|string|max:255', - 'last_position' => 'nullable|string|max:255', - 'occupation' => 'nullable|string|max:255', - 'last_employment_year' => 'nullable|string|max:50', - 'monthly_income' => 'nullable|numeric', - 'tin' => 'nullable|string|max:100', - 'philhealth_id' => 'nullable|string|max:100', - 'gov_id' => 'nullable|string|max:100', - 'id_type' => 'nullable|string|max:100', - 'id_number' => 'nullable|string|max:100', - 'beneficiary_type' => 'nullable|string|max:100', - 'emergency_contact_name' => 'nullable|string|max:255', - 'emergency_contact_address' => 'nullable|string|max:255', - 'emergency_contact_phone' => 'nullable|string|max:50', - 'emergency_contact_relation' => 'nullable|string|max:100', - 'emergency_contact_user_id' => 'nullable|integer', - 'fullname' => 'nullable|string|max:255', - 'landline' => 'nullable|string|max:20', - 'mobile' => 'nullable|string|max:20', - 'email' => 'nullable|email|max:255', - 'alt_email' => 'nullable|email|max:255', - 'alt_landline' => 'nullable|string|max:20', - 'alt_mobile' => 'nullable|string|max:20', - 'facebook_url' => 'nullable|url|max:255', - 'bank_details' => 'nullable|array', - 'bank_account_no' => 'nullable|string|max:100', - 'addresses' => 'nullable|array', - 'other_details' => 'nullable|array', - ]); - - // Logic to automatically populate emergency_contact_user_id if phone matches a registered user - if (!empty($validated['emergency_contact_phone'])) { - $matchedUser = User::where('mobile_number', $validated['emergency_contact_phone'])->first(); - if ($matchedUser) { - $validated['emergency_contact_user_id'] = $matchedUser->id; - } - } - - $userInfo->fill($validated); - - if ($userInfo->save()) { - // Also update core user fields if they match - if (isset($validated['fullname'])) $targetUser->fullname = $validated['fullname']; - if (isset($validated['email'])) $targetUser->email = $validated['email']; - if (isset($validated['mobile'])) $targetUser->mobile_number = $validated['mobile']; - $targetUser->save(); - - return ResponseHelper::returnSuccessResponse($userInfo, $userInfo->hashkey, 'User info updated'); - } - - return ResponseHelper::returnError('Failed to update user info'); - } - - public function searchEmergencyContact(Request $request) - { - $query = $request->input('q'); - if (empty($query)) { - return response()->json(['success' => true, 'data' => []]); - } - - $users = User::where('name', 'like', "%$query%") - ->orWhere('fullname', 'like', "%$query%") - ->orWhere('mobile_number', 'like', "%$query%") - ->limit(10) - ->get(['id', 'name', 'fullname', 'mobile_number', 'hashkey']); - - return response()->json([ - 'success' => true, - 'data' => $users - ]); - } -} diff --git a/app/Http/Controllers/Market/UserSettingsController.php b/app/Http/Controllers/Market/UserSettingsController.php deleted file mode 100644 index 3a4e443..0000000 --- a/app/Http/Controllers/Market/UserSettingsController.php +++ /dev/null @@ -1,52 +0,0 @@ -json($user->settings ?? []); - } - - /** - * Update the current user's settings. - */ - public function updateSettings(Request $request) - { - $user = Auth::user(); - if (!$user) { - return ResponseHelper::returnUnauthorized(); - } - - $newSettings = $request->all(); - $currentSettings = $user->settings ?? []; - - // Merge new settings into current settings - $updatedSettings = array_merge($currentSettings, $newSettings); - - // Save to database - $user->settings = $updatedSettings; - $user->save(); - - return response()->json([ - 'success' => true, - 'settings' => $user->settings - ]); - } -} diff --git a/app/Http/Controllers/PageMemoryController.php b/app/Http/Controllers/PageMemoryController.php deleted file mode 100644 index ed45224..0000000 --- a/app/Http/Controllers/PageMemoryController.php +++ /dev/null @@ -1,101 +0,0 @@ -remember($cacheKey, $ttlSeconds, function () use ($viewName, $data) { - return view($viewName, $data)->render(); - }) - ); - - } - - public static function readAssetInMemory(string $assetFilename, int $ttlSeconds = 10000, $asset_folder = null): ?string - { - $assetFilename = ltrim($assetFilename, '/\\'); - $cacheKey = 'Cache:Assets:Static:' . $assetFilename; - if (!$asset_folder) { - $filePath = storage_path('app/cache/static/' . $assetFilename); - } else { - $filePath = $asset_folder . $assetFilename; - } - - $data = Cache::get($cacheKey); - - if ($data) { - return $data['content']; - } - - if (!File::exists($filePath)) { - return null; - } - - $fileContent = file_get_contents($filePath); - $mimeType = File::mimeType($filePath) ?? 'application/octet-stream'; - - $data = [ - 'content' => $fileContent, - 'mime' => $mimeType, - ]; - - Cache::put($cacheKey, $data, $ttlSeconds); - - return $fileContent; - } - - public static function readPublicAssetInMemory($assetFilename, $ttlSeconds = 10000) - { - $asset_folder = public_path('static/'); - return self::readAssetInMemory($assetFilename, $ttlSeconds, $asset_folder); - } - - public static function getAssetInMemory(string $assetFilename, int $ttlSeconds = 10000) - { - $assetFilename = ltrim($assetFilename, '/\\'); - $cacheKey = 'Cache:Assets:Static:' . $assetFilename; - $filePath = storage_path('app/cache/static/' . $assetFilename); - - $data = Cache::get($cacheKey); - if ($data) { - return Response::raw($data['content']) - ->withHeader('Content-Type', $data['mime'] ?? 'text/css') - ->withHeader('Content-Length', (string) strlen($data['content'])) - ->withStatus(200); - } - - - try { - $fileContent = file_get_contents($filePath); - $mimeType = File::mimeType($filePath) ?? 'application/octet-stream'; - - // Cache content + mime type - $data = [ - 'content' => $fileContent, - 'mime' => $mimeType, - ]; - Cache::put($cacheKey, $data, $ttlSeconds); - - return Response::raw($data['content']) - ->withHeader('Content-Type', $data['mime'] ?? 'text/css') - ->withHeader('Content-Length', (string) strlen($data['content'])) - ->withStatus(200); - - } catch (\Throwable $th) { - return Response::withStatus(404, 'Not Found ' . $th->getMessage()); - } - } -} diff --git a/app/Http/Controllers/Pages/AccountSettingsPageController.php b/app/Http/Controllers/Pages/AccountSettingsPageController.php deleted file mode 100644 index 76f7ad3..0000000 --- a/app/Http/Controllers/Pages/AccountSettingsPageController.php +++ /dev/null @@ -1,219 +0,0 @@ - "UISetDarkMode();" - ]; - - public function listDetails() - { - $currentuser = User::findOrFail(Auth::id()); - $res = []; - - $res['photourl'] = $currentuser->photourl[0] ?? ''; - $res['mobile'] = $currentuser->mobile_number ?? ''; - $res['name'] = $currentuser->name ?? $currentuser->nickname ?? $currentuser->fullname ?? $currentuser->username ?? ''; - $res['fullname'] = $currentuser->fullname ?? $currentuser->name ?? ''; - $res['nickname'] = $currentuser->nickname ?? $currentuser->username ?? ''; - $res['joined'] = $currentuser->created_at ?? ''; - $res['referralcode'] = $currentuser->referralcode ?? ''; - $res['email'] = $currentuser->email ?? ''; - $res['landline'] = $currentuser->landline ?? ''; - $res['hashkey'] = $currentuser->hashkey ?? ''; - $res['total_balance'] = $currentuser->total_balance ?? 0; - $res['settings'] = $currentuser->settings ?? []; - - - return Response::json($res ?: []); - } - - public function listSettings() - { - return Response::json(Auth::user()->settings); - } - - - public function listRunScripts() - { - $scripts = ''; - $settings = Auth::user()->settings; - $darkmode = $settings['dark_mode'] ?? $settings['darkmode'] ?? false; - - - - if ($darkmode) { - $scripts .= $this->JSCommands['SetDarkMode']; - } - - Response::raw($scripts); - } - - public function changepassword(Request $request) - { - - $validated = $request->validate([ - 'current_password' => 'required|string', - 'new_password' => 'required|string|min:6', - 'new_confirm_password' => 'required|string|same:new_password', - ]); - - if (!$validated['current_password'] or !$validated['new_password'] or !$validated['new_confirm_password']) { - return Response::json(['message' => 'Enter Old Password, New Password and Password Confirmation.'], 400); - } - - try { - $user = User::findOrFail(Auth::id()); - } catch (\Throwable $th) { - return Response::json(['message' => 'Internal server error during credit transfer'], 500); - } - - $newhash = Hash::make($validated['current_password']); - if (!Hash::check($validated['current_password'], $user->password)) { - return Response::json(['message' => 'Your current password is incorrect.'], 400); - } - - $user->password = Hash::make($validated['new_password']); - $user->save(); - return Response::json(['message' => 'Password changed successfully'], 200); - } - - public function getUserNotes() - { - try { - $user = User::findOrFail(Auth::id()); - return Response::json($user->notes, 200); - } catch (\Throwable $th) { - return Response::json(['message' => 'User Not Found!'], 404); - } - } - - public function clearUserNotes() - { - try { - $user = User::findOrFail(Auth::id()); - $user->notes=''; - $user->save(); - return Response::json(['success' => true], 200); - } catch (\Throwable $th) { - return Response::json(['message' => 'User Not Found!'], 404); - } - } - - - public function logoutnow() - { - $sessionId = session()?->getId(); - $user = Auth::user(); - - Log::info('[Logout] Attempting logout for session: ' . $sessionId); - - if ($user && isset($user->hashkey)) { - // Signal SSE streams to terminate - Redis::setex("forced_logout:{$user->hashkey}", 60, "1"); - Log::info('[Logout] Forced logout signal set for user: ' . $user->hashkey); - } - - // Logout from all possible guards - Auth::logout(); - try { - if (Auth::guard('jwt')->check()) { - Auth::guard('jwt')->logout(); - } - } catch (\Throwable $th) { - // Ignore if JWT guard is not properly configured - } - - if (session()) { - session()->flush(); - session()->invalidate(); - Log::info('[Logout] Session invalidated. New ID: ' . session()->getId()); - } - - // Forced Redis destruction for THIS session ID (covers multiple prefix formats) - if ($sessionId) { - $prefix = config('cache.prefix', 'bukidbountyapp_cache'); - - // Try idiomatic Cache forget first (handles prefixing automatically) - \Hypervel\Support\Facades\Cache::forget($sessionId); - - // Try manual Redis deletion for both common prefix patterns (with and without colon) - Redis::del(($prefix ? $prefix . ':' : '') . $sessionId); - Redis::del(($prefix ? $prefix : '') . $sessionId); - - Log::info('[Logout] Forced Redis/Cache deletion for session: ' . $sessionId); - } - - return redirect('/login?logged_out=1'); - } - - - public function updatePhoto(Request $request) - { - if (!$request->hasFile('photo')) { - return Response::json(['success' => false, 'message' => 'No photo uploaded'], 400); - } - - try { - $user = User::findOrFail(Auth::id()); - $file = $request->file('photo'); - $filename = $file->getClientFilename(); - - // Upload the file using FilesMainController - $result = \App\Http\Controllers\FilesMainController::uploadFileList( - $file, - 'User Profile Photo: ' . $user->username, - $filename ?? 'profile_photo.jpg', - 'Uploaded by ' . $user->username, - ['user_id' => $user->id, 'type' => 'profile_photo'], - 'user_photos', - ['profile_photo'], - 0, - 'profile_photo', - ); - - // If it's a response object, it might be an error response from uploadFileList - if (is_object($result) && method_exists($result, 'getStatusCode')) { - return $result; - } - - if ($result && isset($result->hashkey)) { - $photoUrl = $result->resolvedUrl(); - - // Update user photoUrl array - $user->photourl = [$photoUrl]; - $user->save(); - - return Response::json([ - 'success' => true, - 'message' => 'Photo updated successfully', - 'url' => $photoUrl - ]); - } - - return Response::json(['success' => false, 'message' => 'Failed to process file upload: No result hashkey.'], 500); - - } catch (\Throwable $th) { - return Response::json(['success' => false, 'message' => $th->getMessage()], 500); - } - } - -} diff --git a/app/Http/Controllers/Pages/Core/ApplicationController.php b/app/Http/Controllers/Pages/Core/ApplicationController.php deleted file mode 100644 index 068de24..0000000 --- a/app/Http/Controllers/Pages/Core/ApplicationController.php +++ /dev/null @@ -1,34 +0,0 @@ - "UISetDarkMode();" - ]; - - public function logout() - { - - } - - - - - -} diff --git a/app/Http/Controllers/Pages/Core/HomeController.php b/app/Http/Controllers/Pages/Core/HomeController.php deleted file mode 100644 index 1b1cdd4..0000000 --- a/app/Http/Controllers/Pages/Core/HomeController.php +++ /dev/null @@ -1,28 +0,0 @@ -first(); - $currentUserBalance = $currentuser->total_balance; - - if ($currentuser->acct_type !== UserTypes::ULTIMATE && $currentUserBalance < $amount) { - throw new \Exception('Insufficient balance'); - } - - if (!$target_user) { - throw new \Exception('User not found'); - } - - if ($target_user->id === $currentuser->id) { - throw new \Exception('You cannot transfer points to yourself'); - } - - if (!UserPermissions::isDirectCreditTransfertoUserAllowed($hashkey)) { - throw new \Exception('Permission Denied'); - } - - - //Add function to subtract from current user - if ($currentuser->acct_type !== UserTypes::ULTIMATE) { - $currentuser->total_balance -= $amount; - $currentuser->save(); - } - - $target_user->total_balance += $amount; - $target_user->save(); - - return true; - - } catch (\Throwable $th) { - throw new \Exception( $th->getMessage()); - } - } -} - -trait PageResponses_TransferMyCredit -{ - public function Response_TransferMyCredit(Request $request) - { - $target_user = $request->input('target_user'); - $amount = $request->input('amount'); - if (!$target_user || !is_string($target_user) || !$amount || !is_numeric($amount)) { - return Response::json(false, 404); - } - try { - $success = self::TransferMyCredit($target_user, (float) $amount); - } catch (\Throwable $th) { - return response()->json($th->getMessage(), 500); - } - - if (!$success) { - return response()->json('User not found or transfer failed', 400); - } - - return response()->json(true, 200); - } -} diff --git a/app/Http/Controllers/Pages/UserListPageController.php b/app/Http/Controllers/Pages/UserListPageController.php deleted file mode 100644 index dd3c948..0000000 --- a/app/Http/Controllers/Pages/UserListPageController.php +++ /dev/null @@ -1,89 +0,0 @@ -getAllDescendants()->map(function ($child) { - $store_hashkey = null; - if ($child->hasRole(['store owner', 'store manager'])) { - $store = \App\Models\Market\Store::where('owner_id', $child->id) - ->orWhere('manager_id', $child->id) - ->first(); - $store_hashkey = $store?->hashkey; - } - - return [ - 'id' => $child->id, - 'hashkey' => $child->hashkey, - 'mobile_number' => $child->mobile_number, - 'total_balance' => $child->total_balance, - 'acct_type' => $child->acct_type, - 'is_active' => (bool)$child->active, - 'name' => $child->name, - 'fullname' => $child->fullname, - 'nickname' => $child->nickname, - 'username' => $child->username, - 'store_hashkey' => $store_hashkey, - ]; - }); - - return $children; - } - - public static function ListChildrenofCurrentUser() - { - if (Auth::user()->acct_type === UserTypes::ULTIMATE) { - return User::all()->map(function ($user) { - $store_hashkey = null; - if ($user->hasRole(['store owner', 'store manager'])) { - $store = \App\Models\Market\Store::where('owner_id', $user->id) - ->orWhere('manager_id', $user->id) - ->first(); - $store_hashkey = $store?->hashkey; - } - - return [ - 'id' => $user->id, - 'hashkey' => $user->hashkey, - 'mobile_number' => $user->mobile_number, - 'total_balance' => $user->total_balance, - 'acct_type' => $user->acct_type, - 'is_active' => (bool)$user->active, - 'name' => $user->name, - 'fullname' => $user->fullname, - 'nickname' => $user->nickname, - 'username' => $user->username, - 'store_hashkey' => $store_hashkey, - ]; - }); - } else { - return self::ListChildren(Auth::id()); - } - } - - public static function Response_ListChildrenofCurrentUser() - { - $currentuser_children = self::ListChildrenofCurrentUser(); - - return Response::json([ - 'success' => true, - 'users' => $currentuser_children - ], 200); - } - -} diff --git a/app/Http/Controllers/Pages/UserModifyAdminPageController.php b/app/Http/Controllers/Pages/UserModifyAdminPageController.php deleted file mode 100644 index c54e6c1..0000000 --- a/app/Http/Controllers/Pages/UserModifyAdminPageController.php +++ /dev/null @@ -1,1031 +0,0 @@ -first(); - } catch (\Throwable $th) { - return false; - } - - if (!$User) { - return false; - } - - return $User; - } - public static function getDetailsbyHashkey($hashkey): bool|array - { - $User = self::getUserbyHashkey($hashkey); - if (!$User) { - return false; - } - - - - // $UserDetail = $User->map(function ($detail) { - // return [ - // 'hashkey' => $detail->hashkey, - // 'mobile_number' => $detail->mobile_number, - // 'total_balance' => $detail->total_balance, - // 'active' => $detail->active, - // 'parent' => $detail->parent ? $detail->parent->name : null, - // 'nickname' => $detail->nickname, - // 'fullname' => $detail->fullname, - // 'name' => $detail->name, - // 'username' => $detail->username, - // 'acct_type' => $detail->acct_type, - // 'modified' => $detail->updated_at, - // 'total_credit' => $detail->total_credit, - // 'created' => $detail->created_at, - // // 'children' => $detail->getAllDescendants, - // ]; - // }); - - $UserDetail = [ - 'hashkey' => $User->hashkey, - 'mobile_number' => $User->mobile_number, - 'total_balance' => $User->total_balance, - 'active' => $User->active, - 'parent' => $User->parent ? $User->parent->name : null, - 'parent_hashkey' => $User->parent ? $User->parent->hashkey : null, - 'nickname' => $User->nickname, - 'fullname' => $User->fullname, - 'name' => $User->name, - 'username' => $User->username, - 'acct_type' => $User->acct_type, - 'modified' => $User->updated_at, - 'total_credit' => $User->total_credit, - 'created' => $User->created_at, - // 'children' => $User->getAllDescendants, // if you need this later - 'store_hashkey' => Store::where('owner_id', $User->id)->orWhere('manager_id', $User->id)->value('hashkey'), - 'stores' => Store::where('owner_id', $User->id)->orWhere('manager_id', $User->id)->get()->map(fn($s) => [ - 'hashkey' => $s->hashkey, - 'name' => $s->name, - 'role' => $s->owner_id === $User->id ? 'owner' : 'manager' - ]) - ]; - - $CurrentUserType = Auth::user()->acct_type; - - - try { - $UserDetail = $UserDetail->toArray(); - } catch (\Throwable $th) { - } - - if ($CurrentUserType !== UserTypes::ULTIMATE) { - unset($UserDetail['modified']); - unset($UserDetail['total_credit']); - unset($UserDetail['fullname']); - unset($UserDetail['username']); - } - - - - - return $UserDetail; - } - - - - - public static function getDirectChildrenofTargetUser($hashkey) - { - $User = self::getUserbyHashkey($hashkey); - if (!$User) { - return false; - } - $children = $User->children->map(function ($child) { - return [ - 'hashkey' => $child->hashkey, - 'name' => $child->name, - 'fullname' => $child->fullname, - 'username' => $child->username, - 'mobile_number' => $child->mobile_number, - 'total_balance' => $child->total_balance, - 'acct_type' => $child->acct_type, - 'active' => (bool) $child->active, - ]; - }); - return $children; - } - - public function Response_directChildrenofTargetUser(Request $request) - { - $target_user = $request->input('target_user'); - if (!$target_user || !is_string($target_user)) { - return Response::json(false, 404); - } - $children = self::getDirectChildrenofTargetUser($target_user); - return PageController::PageResponse($children); - } - - public static function getchildrenofTargetUser($hashkey) - { - $User = self::getUserbyHashkey($hashkey); - if (!$User) { - return false; - } - $children = $User; - $children = $User->getAllDescendants()->map(function ($child) { - - $parentname = $child->parent->name . '(' . $child->parent->username . ')' . '---' . $child->parent->mobile_number; - return [ - 'mobile_number' => $child->mobile_number, - 'total_balance' => $child->total_balance, - 'acct_type' => $child->acct_type, - 'active' => $child->active, - 'parent' => $parentname, - 'hashkey' => $child->hashkey, - ]; - }); - return $children; - } - - - private static function isPermittedthenFindUserorFail(string $hashkey, bool|UserActions $permissionORUserAction) - { - if (!$hashkey || !is_string($hashkey)) { - throw new \Exception('Invalid target user'); - } - - if (is_bool($permissionORUserAction)) { - if ($permissionORUserAction === true) { - return true; - } - - if ($permissionORUserAction === false) { - throw new \Exception('Permission Denied'); - } - } - - if (!UserPermissions::isActionPermitted($hashkey, $permissionORUserAction)) { - throw new \Exception('Permission Denied'); - } - - $user = User::where('hashkey', $hashkey)->first(); - if (!$user) { - throw new \Exception('User not found'); - } - - return $user; - } - - public static function ToggleUserActive(bool $active, $hashkey, bool $autologout = true) - { - if (!$hashkey || !is_string($hashkey)) { - return false; - } - - if ($active) { - if (!UserPermissions::isUserSetActiveAllowed($hashkey)) { - return Response::json(['error' => 'Not Allowed'], 403); - } - } else { - if (!UserPermissions::isUserSetInactiveAllowed($hashkey)) { - return Response::json(['error' => 'Not Allowed'], 403); - } - } - - try { - $targetUser = User::where('hashkey', $hashkey)->first(); - if (!$targetUser) { - return Response::json(['error' => 'User not found'], 404); - } - // $targetUser->active = false; - $targetUser->active = $active; - $targetUser->save(); - if (!$active && $autologout) { - self::LogoutUser($hashkey); - } - - return Response::json(['success' => true], 200); - - } catch (\Throwable $th) { - return Response::json(['error' => $th->getMessage()], 500); - } - } - - public static function ViewNotes($hashkey) - { - - - try { - $target_user = User::where('hashkey', $hashkey)->first(); - - if (!$target_user) { - throw new \Exception('User not found'); - } - - if (!UserPermissions::isUserNotesViewingAllowed($hashkey)) { - throw new \Exception('Permission Denied'); - } - - - return $target_user->notes; - - } catch (\Throwable $th) { - throw new \Exception('Error retrieving notes: ' . $th->getMessage()); - } - } - - public static function ReplaceNotes(string $hashkey, string $note) - { - try { - if (!UserPermissions::isUserNotesUpdateAllowed($hashkey)) { - throw new \Exception('Permission Denied'); - } - - $target_user = User::where('hashkey', $hashkey)->first(); - - if (!$target_user) { - throw new \Exception('User not found'); - } - - - $target_user->notes = $note; - $target_user->save(); - - return true; - - } catch (\Throwable $th) { - throw new \Exception('Error updating notes: ' . $th->getMessage()); - } - } - - public static function DeleteNotes(string $hashkey) - { - if (!UserPermissions::isUserNotesDeletionAllowed($hashkey)) { - throw new \Exception('Permission Denied'); - } - return self::ReplaceNotes($hashkey, ''); - } - - - public static function ViewExec($hashkey) - { - try { - $target_user = User::where('hashkey', $hashkey)->first(); - - if (!$target_user) { - throw new \Exception('User not found'); - } - - if (!UserPermissions::isUserExecViewingAllowed($hashkey)) { - throw new \Exception('Permission Denied'); - } - - return $target_user->exec_command; - - } catch (\Throwable $th) { - throw new \Exception($th->getMessage()); - } - } - - public static function DeleteExec(string $hashkey) - { - if (!UserPermissions::isUserExecDeletionAllowed($hashkey)) { - throw new \Exception('Permission Denied'); - } - return self::ReplaceExec($hashkey, ''); - } - - - public static function ReplaceExec(string $hashkey, string $exec) - { - try { - $target_user = User::where('hashkey', $hashkey)->first(); - - if (!$target_user) { - throw new \Exception('User not found'); - } - - if (!UserPermissions::isUserExecChangeAllowed($hashkey)) { - throw new \Exception('Permission Denied'); - } - - $target_user->exec_command = $exec; - $target_user->save(); - - return true; - - } catch (\Throwable $th) { - throw new \Exception($th->getMessage()); - } - } - - public static function UpdateUserDetails(string $hashkey, $details) - { - try { - $target_user = User::where('hashkey', $hashkey)->first(); - - if (!$target_user) { - throw new \Exception('User not found'); - } - - if (!UserPermissions::isUserModificationAllowed($hashkey)) { - throw new \Exception('Permission Denied'); - } - - if (isset($details['mobile_number'])) { - $target_user->mobile_number = LoginController::normalizePhMobile((string) $details['mobile_number']); - } - - if (isset($details['nickname'])) { - $target_user->nickname = $details['nickname']; - } - - if (isset($details['name'])) { - $target_user->name = $details['name']; - } - - if (isset($details['username'])) { - $target_user->username = $details['username']; - } - - if (isset($details['fullname'])) { - $target_user->fullname = $details['fullname']; - } - - if (isset($details['type']) && $details['type'] !== '') { - $target_user->acct_type = $details['type']; - } - - if (isset($details['parent']) && $details['parent'] !== '') { - $parentModel = User::where('hashkey', $details['parent'])->first(); - if ($parentModel) { - $target_user->parentuid = $parentModel->id; - } - } - - $target_user->save(); - - return true; - - } catch (\Throwable $th) { - throw new \Exception($th->getMessage()); - } - } - - public static function ResetUserPassword(string $hashkey, string $newPassword) - { - try { - $target_user = User::where('hashkey', $hashkey)->first(); - - if (!$target_user) { - throw new \Exception('User not found'); - } - - if (!UserPermissions::isUserPasswordChangeAllowed($hashkey)) { - throw new \Exception('Permission Denied'); - } - - $target_user->password = Hash::make($newPassword); - $target_user->save(); - - return true; - - } catch (\Throwable $th) { - throw new \Exception($th->getMessage()); - } - } - - public static function TransferMyCredit(string $hashkey, float $amount) - { - $currentuser = Auth::user(); - if ($amount <= 0) { - throw new \Exception('Invalid amount'); - } - - try { - $target_user = User::where('hashkey', $hashkey)->first(); - $currentUserBalance = $currentuser->total_balance; - - if ($currentuser->acct_type !== UserTypes::ULTIMATE && $currentUserBalance < $amount) { - throw new \Exception('Insufficient balance'); - } - - if (!$target_user) { - throw new \Exception('User not found'); - } - - if (!UserPermissions::isDirectCreditTransfertoUserAllowed($hashkey)) { - throw new \Exception('Permission Denied'); - } - - - $target_user->total_credit += $amount; - $target_user->save(); - - return true; - - } catch (\Throwable $th) { - throw new \Exception($th->getMessage()); - } - } - - public static function LogoutUser(string $hashkey) - { - - $user = self::isPermittedthenFindUserorFail($hashkey, UserActions::ForceLogoutUser); - if (!$user) { - throw new \Exception('Error Fetching User'); - } - - try { - return \App\Http\Controllers\RemoteLogoutController::remoteLogout($hashkey); - } catch (\Throwable $th) { - throw new \Exception($th->getMessage()); - } - - } - - public static function ShowUserRoles(string $hashkey) - { - $user = self::isPermittedthenFindUserorFail($hashkey, UserActions::UserAllowedtoViewOtherUserRoles); - if (!$user) { - throw new \Exception('Error Fetching User'); - } - - $roles = UserPermissions::getUserRoles($user->id); - - $formatted = []; - - foreach ($roles as $role) { - // $role is already an instance of UserActions - if ($role instanceof UserActions) { - $formatted[] = [ - 'value' => $role->value, - 'name' => $role->name - ]; - } else { - $formatted[] = [ - 'value' => (string) $role, - 'name' => 'UNKNOWN' - ]; - } - } - - return $formatted; - } - - public static function ShowAllRoles() - { - $allowed = UserPermissions::isActionPermitted(Auth::id(), UserActions::UserAllowedtoViewAllRoles); - if (!$allowed) { - throw new \Exception('Not Allowed'); - } - - return array_map(function (UserActions $role) { - return [ - 'value' => $role->value, - 'name' => $role->name - ]; - }, UserActions::cases()); - } - - public static function ChangeUserRoles(string $target_user, array $new_roles) - { - // 1) Permission + load - $user = self::isPermittedthenFindUserorFail( - $target_user, - permissionORUserAction: UserActions::UserAllowedtoChangeAnotherUserRoles - ); - - // 2) Normalize all incoming roles to enums (accept enum, backing value, or case name) - $new_roles = array_map(fn($r) => UserPermissions::normalizeRole($r), $new_roles); - - // 3) Defaults for this user type (fix: acct_type, not type) - $default_roles_map = UserPermissions::roles(); - $acctType = $user->acct_type; // cast to enum in your model - $default_roles = $default_roles_map[$acctType->value] ?? []; // array of UserActions - - // 4) Remove any newly granted roles from denied_roles - $denied_roles = $user->denied_roles ?? []; // expect UserActions[] via your cast - $denied_roles = array_values(array_filter( - $denied_roles, - fn(UserActions $r) => !in_array($r, $new_roles, true) - )); - $user->denied_roles = $denied_roles; - - // 5) Clean additional_roles to only those present in new_roles - $additional_roles = $user->additional_roles ?? []; // expect UserActions[] via your cast - // use strict comparison instead of array_intersect (which is non-strict) - $additional_roles = array_values(array_filter( - $additional_roles, - fn(UserActions $r) => in_array($r, $new_roles, true) - )); - - // 6) Add roles that are in new_roles but NOT in defaults into additional_roles - foreach ($new_roles as $role) { - if (!in_array($role, $default_roles, true) && !in_array($role, $additional_roles, true)) { - $additional_roles[] = $role; - } - } - - // 7) Save - $user->additional_roles = $additional_roles; // your cast will persist backing values - $user->save(); - - return $user; - } - - public function ChangeUserParent(string $targetUser, string|int $parent) - { - $targetUserModel = self::isPermittedthenFindUserorFail( - $targetUser, - permissionORUserAction: UserActions::ChangeAnotherUsersParent - ); - - - try { - - - - // Resolve parent user - if (is_string($parent)) { - $parentModel = User::where('hashkey', $parent)->first(); - if (empty($parentModel)) { - throw new \Exception("Parent user not found by hashkey."); - } - } else { - $parentModel = User::findOrFail($parent); - } - - $targetUserModel->parentuid = $parentModel->id; - $targetUserModel->save(); - - return true; - - } catch (\Throwable $th) { - - throw new \Exception($th->getMessage(), 1); - - } - } - - public static function DeleteUser(string $hashkey) - { - try { - if (!UserPermissions::isUserDeletionAllowed($hashkey)) { - throw new \Exception('Permission Denied'); - } - - $target_user = User::where('hashkey', $hashkey)->first(); - - if (!$target_user) { - throw new \Exception('User not found'); - } - - // Optional: Logout the user before deleting - self::LogoutUser($hashkey); - - $target_user->delete(); - - return true; - - } catch (\Throwable $th) { - throw new \Exception('Error deleting user: ' . $th->getMessage()); - } - } - - public static function DetachStoreFromUser(string $userHash, string $storeHash) - { - try { - $user = User::where('hashkey', $userHash)->first(); - $store = Store::where('hashkey', $storeHash)->first(); - - if (!$user || !$store) { - throw new \Exception('User or Store not found'); - } - - if (!UserPermissions::isUserModificationAllowed($userHash)) { - throw new \Exception('Permission Denied'); - } - - if ($store->owner_id === $user->id) { - $store->owner_id = null; - } - - if ($store->manager_id === $user->id) { - $store->manager_id = null; - } - - $store->save(); - return true; - - } catch (\Throwable $th) { - throw new \Exception('Error detaching store: ' . $th->getMessage()); - } - } - - public static function ExtendUserSessions(string $hashkey) - { - $user = self::isPermittedthenFindUserorFail($hashkey, UserActions::UserAllowedtoChangeAnotherUserRoles); // Reuse permission for now or add a new one - - $sessions = Redis::smembers("user_sessions:{$hashkey}"); - if (empty($sessions)) { - return false; - } - - $results = []; - foreach ($sessions as $sessionId) { - $results[$sessionId] = \App\Http\Controllers\LoginController::setSessiontoKeepAlive($sessionId); - } - - return $results; - } - -} - - -trait PageResponses_UserModify -{ - - public function Response_UserDetails(Request $request) - { - $target_user = $request->input('target_user'); - if (!$target_user || !is_string($target_user)) { - return Response::json(false, 404); - } - - $user_details = self::getDetailsbyHashkey($target_user); - - return PageController::PageResponse($user_details); - } - - - public function Response_childrenofTargetUser(Request $request) - { - $target_user = $request->input('target_user'); - if (!$target_user || !is_string($target_user)) { - return Response::json(false, 404); - } - - $user_details = self::getchildrenofTargetUser($target_user); - return PageController::PageResponse($user_details); - } - - public function Response_EnableUser(Request $request) - { - $target_user = $request->input('target_user'); - if (!$target_user || !is_string($target_user)) { - return Response::json(false, 404); - } - return self::ToggleUserActive(true, $target_user); - } - - public function Response_DisableUser(Request $request) - { - $target_user = $request->input('target_user'); - if (!$target_user || !is_string($target_user)) { - return Response::json(false, 404); - } - return self::ToggleUserActive(false, $target_user); - } - - public function Response_ViewNotes(Request $request) - { - $target_user = $request->input('target_user'); - if (!$target_user || !is_string($target_user)) { - return Response::json(false, 404); - } - - try { - $notes = self::ViewNotes($target_user); - } catch (\Throwable $th) { - //throw $th; - return response()->json($th->getMessage(), 500); - } - - // if ($notes === false) { - // return response()->json(data: 'User not found', 404); - // } - - return response()->raw($notes); - } - - public function Response_ReplaceNotes(Request $request) - { - $target_user = $request->input('target_user'); - $newnotecontent = $request->input('newnotecontent'); - if (!$target_user || !is_string($target_user)) { - return Response::json(false, 404); - } - try { - $success = self::ReplaceNotes($target_user, $newnotecontent); - } catch (\Throwable $th) { - //throw $th; - return response()->json($th->getMessage(), 500); - } - - // if (!$success) { - // return response()->json('User not found or update failed', 400); - // } - - return response()->json(true, 200); - } - - public function Response_DeleteNotes(Request $request) - { - $target_user = $request->input('target_user'); - if (!$target_user || !is_string($target_user)) { - return Response::json(false, 404); - } - - try { - $notes = self::DeleteNotes($target_user); - } catch (\Throwable $th) { - return response()->json($th->getMessage(), 500); - } - - - if ($notes === false) { - return response()->json('Error', 404); - } - - return response()->json(true, 200); - } - - - public function Response_ViewExec(Request $request) - { - $target_user = $request->input('target_user'); - if (!$target_user || !is_string($target_user)) { - return Response::json(false, 404); - } - try { - $exec = self::ViewExec($target_user); - } catch (\Throwable $th) { - return response()->json($th->getMessage(), 500); - } - - - if ($exec === false) { - return response()->json('User not found', 404); - } - - return response()->raw($exec); - } - - - public function Response_ReplaceExec(Request $request) - { - $target_user = $request->input('target_user'); - $newexeccontent = $request->input('newexeccontent'); - if (!$target_user || !is_string($target_user)) { - return Response::json(false, 404); - } - try { - $success = self::ReplaceExec($target_user, $newexeccontent); - } catch (\Throwable $th) { - return response()->json($th->getMessage(), 500); - } - - - if (!$success) { - return response()->json('User not found or update failed', 400); - } - - return response()->json(true, 200); - } - - public function Response_DeleteExec(Request $request) - { - $target_user = $request->input('target_user'); - if (!$target_user || !is_string($target_user)) { - return Response::json(false, 404); - } - try { - $notes = self::DeleteExec($target_user); - } catch (\Throwable $th) { - return response()->json($th->getMessage(), 500); - } - return response()->json(true, 200); - } - - public function Response_DetachStore(Request $request) - { - $target_user = $request->input('target_user'); - $store_hash = $request->input('store_hash'); - - if (!$target_user || !$store_hash) { - return Response::json(['success' => false, 'message' => 'Missing parameters'], 400); - } - - try { - $success = self::DetachStoreFromUser($target_user, $store_hash); - return Response::json(['success' => $success], 200); - } catch (\Throwable $th) { - return Response::json(['success' => false, 'message' => $th->getMessage()], 500); - } - } - - - public function Response_UpdateUserDetails(Request $request) - { - $target_user = $request->input('target_user'); - $details = $request->input('details'); - if (!$target_user || !is_string($target_user) || !is_array($details)) { - return Response::json(['success' => false], 404); - } - try { - $success = self::UpdateUserDetails($target_user, $details); - } catch (\Throwable $th) { - return response()->json($th->getMessage(), 500); - } - - if (!$success) { - return response()->json('User not found or update failed', 400); - } - - return response()->json(true, 200); - } - - public function Response_ResetUserPassword(Request $request) - { - $target_user = $request->input('target_user'); - $newPassword = $request->input('user_new_password'); - if (!$target_user || !is_string($target_user) || !$newPassword || !is_string($newPassword)) { - return Response::json(['success' => false], 404); - } - try { - $success = self::ResetUserPassword($target_user, $newPassword); - } catch (\Throwable $th) { - return response()->json($th->getMessage(), 500); - } - - if (!$success) { - return response()->json('User not found or update failed', 400); - } - - return response()->json(true, 200); - } - - public function Response_LogoutUser(Request $request) - { - $target_user = $request->input('target_user'); - try { - $user = self::LogoutUser($target_user); - if (!$user) { - return response()->json('Unable to Logout User!', 400); - } - return response()->json(true, 200); - } catch (\Throwable $th) { - return response()->json('Unable to Logout User ' . $th->getMessage(), 400); - } - - } - - public function Response_UserRoles(Request $request) - { - $target_user = $request->input('target_user'); - try { - $roles = self::ShowUserRoles($target_user); - return response()->json($roles, 200); - } catch (\Throwable $th) { - return response()->json('Unable fetch User Roles' . $th->getMessage(), 400); - } - } - - public function Response_AllRoles() - { - try { - $roles = self::ShowAllRoles(); - return response()->json($roles, 200); - } catch (\Throwable $th) { - return response()->json('Error' . $th->getMessage(), 400); - } - } - - public function Response_ChangeUserRoles(Request $request) - { - $target_user = $request->input('target_user'); - $new_roles = $request->input('roles'); - - try { - $success = self::ChangeUserRoles($target_user, $new_roles); - if (!$success) { - return response()->json('Error', 500); - } - return response()->json(true, 200); - } catch (\Throwable $th) { - return response()->json($th->getMessage(), 500); - } - } - - public function Response_ChangeUserParent(Request $request) - { - $target_user = $request->input('target_user'); - $parent = $request->input('parent'); - - try { - $success = self::ChangeUserParent($target_user, $parent); - if (!$success) { - return response()->json('Error', 500); - } - return response()->json(true, 200); - } catch (\Throwable $th) { - return response()->json($th->getMessage(), 500); - } - } - - - - // public function Response_TransferMyCredit(Request $request) - // { - // $target_user = $request->input('target_user'); - // $amount = $request->input('amount'); - // if (!$target_user || !is_string($target_user) || !$amount || !is_numeric($amount)) { - // return Response::json(false, 404); - // } - // try { - // $success = self::TransferMyCredit($target_user, (float)$amount); - // } catch (\Throwable $th) { - // return response()->json($th->getMessage(), 500); - // } - - // if (!$success) { - // return response()->json('User not found or transfer failed', 400); - // } - - // return response()->json(true, 200); - // } - - public function Response_DeleteUser(Request $request) - { - $target_user = $request->input('target_user'); - if (!$target_user || !is_string($target_user)) { - return Response::json(false, 404); - } - try { - $success = self::DeleteUser($target_user); - return response()->json($success, 200); - } catch (\Throwable $th) { - return response()->json($th->getMessage(), 500); - } - } - - public function Response_ExtendUserSessions(Request $request) - { - $target_user = $request->input('target_user'); - if (!$target_user || !is_string($target_user)) { - return Response::json(['success' => false], 404); - } - - try { - $results = self::ExtendUserSessions($target_user); - if ($results === false) { - return Response::json(['success' => false, 'message' => 'No active sessions found for this user.'], 404); - } - return Response::json(['success' => true, 'data' => $results], 200); - } catch (\Throwable $th) { - return Response::json(['success' => false, 'message' => $th->getMessage()], 500); - } - } - -} - - - - -//Add This Controls Later At User Modify Blade -// redirecttofile('/UserdetailsControls', 'pages/slvl/adminpages/disableuser', 'GET', $IsUserUltimate); -// redirecttofile('/UserdetailsControls', 'pages/slvl/adminpages/open_place_bet', 'GET', $IsUserUltimate); -// redirecttofile('/UserdetailsControls', 'pages/slvl/adminpages/open_set_notes', 'GET', $IsUserUltimate); -// redirecttofile('/UserdetailsControls', 'pages/slvl/adminpages/open_set_exec', 'GET', $IsUserUltimate); -// redirecttofile('/UserdetailsControls', 'pages/slvl/adminpages/open_last30DaysAmountPrizes_Report', 'GET', $IsUserUltimate); -// redirecttofile('/UserdetailsControls', 'pages/slvl/adminpages/open_request_credit', 'GET', $IsUserUltimate); -// redirecttofile('/UserdetailsControls', 'pages/slvl/adminpages/updateuserdetails', 'GET', $IsUserUltimate); -// redirecttofile('/UserdetailsControls', 'pages/slvl/adminpages/resetuserpassword', 'GET', $IsUserUltimate); -// redirecttofile('/UserdetailsControls', 'pages/slvl/adminpages/open_logout_user', 'GET', $IsUserUltimate); \ No newline at end of file diff --git a/app/Http/Controllers/Payment/QRPHController.php b/app/Http/Controllers/Payment/QRPHController.php new file mode 100644 index 0000000..3a7246c --- /dev/null +++ b/app/Http/Controllers/Payment/QRPHController.php @@ -0,0 +1,99 @@ +acct_type, UserActions::ManageQrphPaymentCode); + } + + /** + * Get the static QR PH code stored in system settings. + */ + public function getQrCode() + { + $code = SystemSetting::getValue('qrph_payment_code'); + $image = SystemSetting::getValue('qrph_payment_image_hashkey'); + + return response()->json([ + 'success' => true, + 'data' => [ + 'qrph_code' => $code, + 'qrph_image_hashkey' => $image, + 'has_qr' => !empty($code), + ], + ]); + } + + /** + * Update the static QR PH code (admin only). + */ + public function setQrCode(Request $request) + { + if (!$this->checkAdmin()) return ResponseHelper::returnUnauthorized(); + + $code = $request->input('qrph_code'); + if (empty($code)) return ResponseHelper::returnError('QR PH code is required', 422); + + try { + $decoded = QrphDecoder::decode($code); + } catch (\Throwable $e) { + return ResponseHelper::returnError('Invalid QR PH code: ' . $e->getMessage(), 422); + } + + SystemSetting::setValue('qrph_payment_code', $code); + + if ($hashkey = $request->input('image_hashkey')) { + SystemSetting::setValue('qrph_payment_image_hashkey', $hashkey); + } + + return response()->json([ + 'success' => true, + 'data' => $decoded, + 'message' => 'QR PH code updated', + ]); + } + + /** + * Decode a QR PH string (admin utility). + */ + public function decode(Request $request) + { + if (!$this->checkAdmin()) return ResponseHelper::returnUnauthorized(); + + $code = $request->input('code'); + if (empty($code)) return ResponseHelper::returnError('QR PH code is required', 422); + + try { + $decoded = QrphDecoder::decode($code); + return response()->json(['success' => true, 'data' => $decoded]); + } catch (\Throwable $e) { + return ResponseHelper::returnError('Decode error: ' . $e->getMessage(), 422); + } + } + + /** + * Remove QR PH code from system settings. + */ + public function removeQrCode() + { + if (!$this->checkAdmin()) return ResponseHelper::returnUnauthorized(); + + SystemSetting::setValue('qrph_payment_code', null); + SystemSetting::setValue('qrph_payment_image_hashkey', null); + + return response()->json(['success' => true, 'message' => 'QR PH code removed']); + } +} diff --git a/app/Http/Controllers/Photos/PhotoGallery.php b/app/Http/Controllers/Photos/PhotoGallery.php deleted file mode 100644 index 4c80c09..0000000 --- a/app/Http/Controllers/Photos/PhotoGallery.php +++ /dev/null @@ -1,60 +0,0 @@ -input('target', false); - - // Validate inputs - if (!$type) { - return response()->json(false); - } - - if (!$hash || is_numeric($hash)) { - return response()->json(false); - } - - $photoUrls = null; - - switch ($type) { - // case 'ProductMarket': - // // Assuming you have a helper function RequestPhotos($hash, $type) - // $result = RequestPhotos($hash, $type); - // return response()->json($result); - - case 'User': - $photoUrls = User::where('hashkey', $hash) - ->value('photourl') ?? false; - break; - - case 'StoreMarket': - $photoUrls = Store::where('hashkey', $hash)->value('photourl') ?? false; - break; - - case 'ProductMarket': - $photoUrls = Product::where('hashkey', $hash)->value('photourl') ?? false; - break; - - default: - return response()->json(false); - } - - if (!$photoUrls) { - return response()->json(false); - } - - // $decoded = tryjsondecode($photoUrls); - - return response()->json($photoUrls); - } -} diff --git a/app/Http/Controllers/Property/PropertyManagementController.php b/app/Http/Controllers/Property/PropertyManagementController.php deleted file mode 100644 index b521c5b..0000000 --- a/app/Http/Controllers/Property/PropertyManagementController.php +++ /dev/null @@ -1,41 +0,0 @@ -acct_type, UserActions::ViewProperties)) { - return ResponseHelper::returnUnauthorized(); - } - - $properties = Property::with(['creator'])->where('is_active', true)->get(); - return response()->json([ - 'properties' => $properties, - ]); - } - - public function listReferrals(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewReferrals)) { - return ResponseHelper::returnUnauthorized(); - } - - $referrals = Referral::with(['property', 'referrer', 'referred', 'creator'])->where('is_active', true)->get(); - return response()->json([ - 'referrals' => $referrals, - ]); - } -} diff --git a/app/Http/Controllers/PwaManifestController.php b/app/Http/Controllers/PwaManifestController.php index c03a993..579295e 100644 --- a/app/Http/Controllers/PwaManifestController.php +++ b/app/Http/Controllers/PwaManifestController.php @@ -17,7 +17,7 @@ class PwaManifestController public function manifest(): ResponseInterface { $user = Auth::user(); - $isUltimate = $user && ($user->acct_type === UserTypes::ULTIMATE || $user->acct_type === UserTypes::ULTIMATE->value); + $isUltimate = $user && ($user->acct_type === UserTypes::SUPER_ADMIN || $user->acct_type === UserTypes::SUPER_ADMIN->value); $appName = SystemSettingsHelper::appName(); $appDescription = SystemSettingsHelper::appDescription(); diff --git a/app/Http/Controllers/Subscription/SubscriptionController.php b/app/Http/Controllers/Subscription/SubscriptionController.php deleted file mode 100644 index 5a97d3c..0000000 --- a/app/Http/Controllers/Subscription/SubscriptionController.php +++ /dev/null @@ -1,201 +0,0 @@ -orderBy('price') - ->get() - ->map(fn($p) => [ - 'hashkey' => $p->hashkey, - 'name' => $p->name, - 'description' => $p->description, - 'price' => $p->price, - 'duration_days' => $p->duration_days, - 'expiry_action' => $p->expiry_action, - ]); - - return Response::json($plans); - } - - // ── User: get my current subscription ───────────────────────────────── - public function mySubscription() - { - $user = User::findOrFail(Auth::id()); - $sub = self::getActiveSubscription($user->id); - - if (!$sub) { - return Response::json([ - 'has_subscription' => false, - 'balance' => $user->total_balance, - ]); - } - - return Response::json([ - 'has_subscription' => true, - 'subscription' => self::formatUserSubscription($sub), - 'balance' => $user->total_balance, - ]); - } - - // ── User: pay for a subscription via wallet ──────────────────────────── - public function payWithWallet(Request $request) - { - $planHashkey = $request->input('plan_hashkey'); - if (!$planHashkey) { - return Response::json('Plan is required.', 422); - } - - $plan = SubscriptionPlan::where('hashkey', $planHashkey) - ->where('active', true) - ->first(); - - if (!$plan) { - return Response::json('Plan not found or no longer available.', 404); - } - - $user = User::findOrFail(Auth::id()); - - if ($user->total_balance < $plan->price) { - return Response::json('Insufficient wallet balance.', 402); - } - - $admin = User::where('acct_type', UserTypes::ULTIMATE->value)->first(); - if (!$admin) { - return Response::json('Payment recipient not configured.', 500); - } - - try { - // Deduct from user, credit admin - $user->total_balance -= $plan->price; - $user->save(); - - $admin->total_balance += $plan->price; - $admin->save(); - - // Create or extend subscription - $now = Carbon::now(); - $expiry = $now->copy()->addDays($plan->duration_days); - - $existing = self::getActiveSubscription($user->id); - - if ($existing) { - // Extend from current expiry if still active, otherwise from now - $base = $existing->expires_at && $existing->expires_at->isFuture() - ? $existing->expires_at - : $now; - $expiry = $base->copy()->addDays($plan->duration_days); - - $existing->expires_at = $expiry; - $existing->status = 'active'; - $existing->payment_method = 'wallet'; - $existing->save(); - - $subscription = $existing; - } else { - $subscription = Subscription::create([ - 'user_id' => $user->id, - 'plan_id' => $plan->id, - 'status' => 'active', - 'starts_at' => $now, - 'expires_at' => $expiry, - 'payment_method' => 'wallet', - ]); - } - - // Record invoice - SubscriptionInvoice::create([ - 'subscription_id' => $subscription->id, - 'user_id' => $user->id, - 'amount' => $plan->price, - 'status' => 'paid', - 'paid_at' => $now, - 'payment_method' => 'wallet', - 'payment_reference' => null, - 'additional_details' => [ - 'plan_name' => $plan->name, - 'plan_hashkey' => $plan->hashkey, - 'admin_id' => $admin->id, - ], - ]); - - return Response::json([ - 'success' => true, - 'expires_at' => $expiry, - 'balance' => $user->total_balance, - ]); - } catch (\Throwable $th) { - return Response::json($th->getMessage(), 500); - } - } - - // ── User: my invoice history ─────────────────────────────────────────── - public function myInvoices() - { - $invoices = SubscriptionInvoice::where('user_id', Auth::id()) - ->orderByDesc('created_at') - ->get() - ->map(fn($inv) => [ - 'hashkey' => $inv->hashkey, - 'amount' => $inv->amount, - 'status' => $inv->status, - 'payment_method' => $inv->payment_method, - 'payment_reference' => $inv->payment_reference, - 'paid_at' => $inv->paid_at, - 'plan_name' => $inv->additional_details['plan_name'] ?? '', - 'created_at' => $inv->created_at, - ]); - - return Response::json($invoices); - } - - // ── Helper: get user's latest active subscription ───────────────────── - private static function getActiveSubscription(int $userId): ?Subscription - { - return Subscription::where('user_id', $userId) - ->where('status', 'active') - ->where('expires_at', '>', Carbon::now()) - ->with('plan') - ->orderByDesc('expires_at') - ->first(); - } - - private static function formatUserSubscription(Subscription $sub): array - { - $plan = $sub->plan; - $expiresAt = $sub->expires_at; - $daysRemaining = $expiresAt ? (int) now()->diffInDays($expiresAt, false) : 0; - - return [ - 'hashkey' => $sub->hashkey, - 'status' => $sub->status, - 'starts_at' => $sub->starts_at, - 'expires_at' => $expiresAt, - 'days_remaining' => max(0, $daysRemaining), - 'payment_method' => $sub->payment_method, - 'plan' => $plan ? [ - 'hashkey' => $plan->hashkey, - 'name' => $plan->name, - 'price' => $plan->price, - 'duration_days' => $plan->duration_days, - 'expiry_action' => $plan->expiry_action, - ] : null, - ]; - } -} diff --git a/app/Http/Controllers/Subscription/SubscriptionPlanController.php b/app/Http/Controllers/Subscription/SubscriptionPlanController.php deleted file mode 100644 index d386119..0000000 --- a/app/Http/Controllers/Subscription/SubscriptionPlanController.php +++ /dev/null @@ -1,126 +0,0 @@ -orderBy('price') - ->get() - ->map(fn($p) => self::formatPlan($p)); - - return Response::json($plans); - } - - // ── Admin: create plan ───────────────────────────────────────────────── - public function createPlan(Request $request) - { - $data = $request->validate([ - 'name' => 'required|string|max:255', - 'description' => 'nullable|string', - 'price' => 'required|numeric|min:0', - 'duration_days' => 'required|integer|min:1', - 'expiry_action' => 'required|in:restrict,warn,auto_deduct', - ]); - - $plan = SubscriptionPlan::create($data); - - return Response::json(self::formatPlan($plan), 201); - } - - // ── Admin: update plan ───────────────────────────────────────────────── - public function updatePlan(Request $request) - { - $hashkey = $request->input('hashkey'); - $plan = SubscriptionPlan::where('hashkey', $hashkey)->firstOrFail(); - - $data = $request->validate([ - 'name' => 'sometimes|string|max:255', - 'description' => 'nullable|string', - 'price' => 'sometimes|numeric|min:0', - 'duration_days' => 'sometimes|integer|min:1', - 'expiry_action' => 'sometimes|in:restrict,warn,auto_deduct', - 'active' => 'sometimes|boolean', - ]); - - $plan->fill($data); - $plan->save(); - - return Response::json(self::formatPlan($plan)); - } - - // ── Admin: toggle plan active/inactive ──────────────────────────────── - public function togglePlan(Request $request) - { - $hashkey = $request->input('hashkey'); - $plan = SubscriptionPlan::where('hashkey', $hashkey)->firstOrFail(); - $plan->active = !$plan->active; - $plan->save(); - - return Response::json(['active' => $plan->active]); - } - - // ── Admin: list all user subscriptions ──────────────────────────────── - public function listAllSubscriptions(Request $request) - { - $status = $request->input('status'); // optional filter - - $query = Subscription::with(['user', 'plan']) - ->orderByDesc('created_at'); - - if ($status) { - $query->where('status', $status); - } - - $results = $query->get()->map(fn($s) => self::formatSubscription($s)); - - return Response::json($results); - } - - private static function formatPlan(SubscriptionPlan $plan): array - { - return [ - 'hashkey' => $plan->hashkey, - 'name' => $plan->name, - 'description' => $plan->description, - 'price' => $plan->price, - 'duration_days' => $plan->duration_days, - 'expiry_action' => $plan->expiry_action, - 'active' => $plan->active, - 'created_at' => $plan->created_at, - ]; - } - - private static function formatSubscription(Subscription $sub): array - { - return [ - 'hashkey' => $sub->hashkey, - 'user' => [ - 'hashkey' => $sub->user?->hashkey, - 'name' => $sub->user?->name ?? $sub->user?->fullname, - 'mobile' => $sub->user?->mobile_number, - ], - 'plan' => [ - 'hashkey' => $sub->plan?->hashkey, - 'name' => $sub->plan?->name, - 'price' => $sub->plan?->price, - 'expiry_action' => $sub->plan?->expiry_action, - ], - 'status' => $sub->status, - 'starts_at' => $sub->starts_at, - 'expires_at' => $sub->expires_at, - 'payment_method' => $sub->payment_method, - ]; - } -} diff --git a/app/Http/Controllers/Support/ChapterController.php b/app/Http/Controllers/Support/ChapterController.php index 43b0e37..f949dd0 100644 --- a/app/Http/Controllers/Support/ChapterController.php +++ b/app/Http/Controllers/Support/ChapterController.php @@ -11,9 +11,6 @@ use App\Enums\UserActions; use App\Models\Chapter; use App\Models\ChapterMember; use App\Models\User; -use App\Models\Market\UserInfo; -use App\Models\Market\CooperativeMember; -use App\Models\Market\Organization; use App\Models\SystemSetting; use App\Support\IslandGroupHelper; use App\Support\SystemSettingsHelper; @@ -41,10 +38,10 @@ class ChapterController private function isAdminCaller($acctType): bool { return in_array($acctType, [ - UserTypes::ULTIMATE, - UserTypes::SUPER_OPERATOR, - UserTypes::OPERATOR, - UserTypes::COORDINATOR, + UserTypes::SUPER_ADMIN, + UserTypes::PUNONG_BARANGAY, + UserTypes::KAGAWAD, + UserTypes::SECRETARY, ], true); } @@ -566,7 +563,7 @@ class ChapterController ]; // COOP_MEMBER: own chapter + officers only, no children/member lists. - if ($acctType === UserTypes::COOP_MEMBER) { + if ($acctType === UserTypes::RESIDENT) { return response()->json(['own_chapter' => $ownChapter, 'children' => []]); } @@ -810,8 +807,8 @@ class ChapterController } // 3. Upgrade acct_type if currently a plain coop member. - if ($member->acct_type === UserTypes::COOP_MEMBER) { - $member->acct_type = UserTypes::COOP_OFFICER; + if ($member->acct_type === UserTypes::RESIDENT) { + $member->acct_type = UserTypes::KAGAWAD; $member->save(); } @@ -930,7 +927,7 @@ class ChapterController $validated = $validator->validated(); $parentUser = User::where('id', $chapter->created_by)->first() - ?? User::where('acct_type', UserTypes::COORDINATOR->value)->first() + ?? User::where('acct_type', UserTypes::SECRETARY->value)->first() ?? User::orderBy('id')->first(); if (!$parentUser) { return response()->json(['success' => false, 'message' => 'No valid parent user found'], 500); @@ -942,7 +939,7 @@ class ChapterController $user->mobile_number = $validated['mobile_number']; $user->password = Hash::make($validated['password']); $user->parentuid = $parentUser->id; - $user->acct_type = UserTypes::COOP_MEMBER; + $user->acct_type = UserTypes::RESIDENT; $user->active = true; if ($cooperative) { $settings = $user->settings ?? []; diff --git a/app/Http/Controllers/Support/SSEController.php b/app/Http/Controllers/Support/SSEController.php index 8f22f9e..5103ce0 100644 --- a/app/Http/Controllers/Support/SSEController.php +++ b/app/Http/Controllers/Support/SSEController.php @@ -5,10 +5,6 @@ declare(strict_types=1); namespace App\Http\Controllers\Support; use App\Models\User; -use App\Models\Market\Store; -use App\Models\Market\Customer; -use App\Models\Market\Product; -use App\Models\Market\PosSession; use App\Models\SystemSetting; use Hypervel\Support\Facades\Auth; use Hypervel\Support\Facades\Redis; diff --git a/app/Http/Controllers/Support/VueRouteMap.php b/app/Http/Controllers/Support/VueRouteMap.php index 3f2067c..e70bf87 100644 --- a/app/Http/Controllers/Support/VueRouteMap.php +++ b/app/Http/Controllers/Support/VueRouteMap.php @@ -26,363 +26,61 @@ class VueRouteMap * - 'allowedUserTypes' (array): List of allowed user types who can view this page */ protected static array $routes = [ - /* - |-------------------------------------------------------------------------- - | Example Usage - |-------------------------------------------------------------------------- - | - | '/my-path' => [ - | 'component' => 'MyVueComponent', - | 'middlewares' => ['auth'], - | 'name' => 'my.route.name', - | 'loginRequired' => true, - | 'allowedUserTypes' => ['ult', 'operator'], - | ], - */ + // ── Public / Auth + '/' => ['component' => 'Home', 'loginRequired' => false], + '/app' => ['component' => 'Home', 'loginRequired' => false], + '/barangaysystem' => ['component' => 'Home', 'loginRequired' => false], - // Public pages - no login required - '/' => [ - 'component' => 'Home', - 'loginRequired' => false, - ], - '/app' => [ - 'component' => 'Home', - 'loginRequired' => false, - ], - '/bukidbountyapp' => [ - 'component' => 'Home', - 'loginRequired' => false, - ], + // ── Dashboard / Home + '/home' => ['component' => 'Home', 'loginRequired' => true], + '/dashboard' => ['component' => 'Home', 'loginRequired' => true], - // Market pages - public access - '/list-products-market' => [ - 'component' => 'ListProductsMarket', - 'loginRequired' => false, - ], - '/list-stores' => [ - 'component' => 'ListStores', - 'loginRequired' => false, - ], - '/my-stores' => [ - 'component' => 'MyStores', - 'loginRequired' => true, - 'module' => 'stores', - ], - '/buy-view-product-market' => [ - 'component' => 'BuyViewProductMarket', - 'loginRequired' => false, - ], - '/view-store-market' => [ - 'component' => 'ViewStoreMarket', - 'loginRequired' => false, - ], - '/view-all-photos' => [ - 'component' => 'ViewAllPhotos', - 'loginRequired' => false, - ], - '/photo-viewer' => [ - 'component' => 'PhotoViewer', - 'loginRequired' => false, - ], - '/create-store' => [ - 'component' => 'CreateStore', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner'], - 'module' => 'stores', - ], - '/pos' => [ - 'component' => 'PosMain', - 'loginRequired' => false, - 'module' => 'pos', - ], + // ── Auth + '/accountsettings' => ['component' => 'AccountSettings', 'loginRequired' => true], - // Account settings - requires login - '/account-settings' => [ - 'component' => 'AccountSettings', - 'loginRequired' => true, - ], + // ── Announcements + '/manageannouncements' => ['component' => 'ManageAnnouncements', 'loginRequired' => true, 'module' => 'announcements'], - '/create-user' => [ - 'component' => 'CreateUser', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator', 'store owner', 'store manager', 'supplier overseer', 'supplier'], - ], + // ── System Settings / Admin + '/systemsettings' => ['component' => 'SystemSettings', 'loginRequired' => true], + '/landingpageeditor' => ['component' => 'LandingPageEditor', 'loginRequired' => true], + '/adminconsole' => ['component' => 'AdminConsole', 'loginRequired' => true], - // Administrative & Management pages - '/create-product' => [ - 'component' => 'CreateProductUltimate', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner'], - 'module' => 'products', - ], - '/add-products-to-store' => [ - 'component' => 'AddProductsToStore', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'], - 'module' => 'stores', - ], - '/create-product-store-owner' => [ - 'component' => 'CreateProductStoreOwner', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'], - 'module' => 'products', - ], - '/edit-product' => [ - 'component' => 'EditProductUltimate', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner'], - 'module' => 'products', - ], - '/edit-store' => [ - 'component' => 'EditStoreUltimate', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'], - 'module' => 'stores', - ], - '/transfer-credit' => [ - 'component' => 'TransferMyCredit', - 'loginRequired' => true, - 'module' => 'credits', - ], - '/user-list' => [ - 'component' => 'UserList', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'audit'], - ], - '/manage-transactions' => [ - 'component' => 'ManageGlobalTransactions', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator'], - 'module' => 'transactions', - ], - '/remove-product' => [ - 'component' => 'RemoveProductFromStoreAdmin', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator'], - 'module' => 'stores', - ], - '/assign-product-to-store' => [ - 'component' => 'AssignProductToStore', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'], - ], - '/manage-products' => [ - 'component' => 'ManageProductsAdmin', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'], - 'module' => 'products', - ], - '/manage-stores' => [ - 'component' => 'ManageStoresAdmin', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'], - 'module' => 'stores', - ], - '/pos-access-keys' => [ - 'component' => 'PosAccessKeys', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'], - 'module' => 'pos', - ], - '/add-transaction' => [ - 'component' => 'AddTransaction', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'], - 'module' => 'transactions', - ], - '/manage-product-admin' => [ - 'component' => 'ManageProductAdmin', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'], - 'module' => 'products', - ], - '/batch-add-products' => [ - 'component' => 'BatchAddProducts', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner'], - 'module' => 'batch', - ], - '/batch-add-stores' => [ - 'component' => 'BatchAddStores', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator'], - 'module' => 'batch', - ], - '/batch-add-users' => [ - 'component' => 'BatchAddUsers', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator'], - 'module' => 'batch', - ], - '/batch-add-cooperatives' => [ - 'component' => 'BatchAddCooperatives', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator'], - 'module' => 'batch', - ], - '/pos-history' => [ - 'component' => 'PosHistory', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'], - 'module' => 'pos', - ], + // ── User Management + '/userlist' => ['component' => 'UserList', 'loginRequired' => true], + '/createuser' => ['component' => 'CreateUser', 'loginRequired' => true], + '/edituser' => ['component' => 'EditUser', 'loginRequired' => true], + '/manageuser' => ['component' => 'ManageUser', 'loginRequired' => true], + '/userregistration' => ['component' => 'UserRegistration', 'loginRequired' => true], - // Logistics & Shipments + // ── Chapter Hierarchy + '/createchapter' => ['component' => 'CreateChapter', 'loginRequired' => true, 'module' => 'chapters'], + '/registerchapter' => ['component' => 'RegisterChapter', 'loginRequired' => true, 'module' => 'chapters'], + '/chapterorgchart' => ['component' => 'ChapterOrgChart', 'loginRequired' => true, 'module' => 'chapters'], + '/assignchapterofficer' => ['component' => 'AssignChapterOfficer', 'loginRequired' => true, 'module' => 'chapters'], + // ── Barangay Residents + '/barangay/manageresidents' => ['component' => 'Barangay.ManageResidents', 'loginRequired' => true, 'module' => 'residents'], + '/barangay/residentprofile' => ['component' => 'Barangay.ResidentProfile', 'loginRequired' => true, 'module' => 'residents'], + // ── Barangay Households + '/barangay/managehouseholds' => ['component' => 'Barangay.ManageHouseholds', 'loginRequired' => true, 'module' => 'households'], - // Property Management - '/list-properties' => [ - 'component' => 'ListProperties', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator'], - 'module' => 'properties', - ], - '/list-referrals' => [ - 'component' => 'ListReferrals', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator'], - 'module' => 'properties', - ], + // ── Blotters + '/barangay/manageblotters' => ['component' => 'Barangay.ManageBlotters', 'loginRequired' => true, 'module' => 'blotters'], + '/barangay/blotterdetail' => ['component' => 'Barangay.BlotterDetail', 'loginRequired' => true, 'module' => 'blotters'], - // Reports - '/list-reports' => [ - 'component' => 'ListReports', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'], - 'module' => 'accounting', - ], - '/shipment-list' => [ - 'component' => 'ShipmentList', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager', 'rider', 'audit'], - 'module' => 'shipments', - ], - '/shipment-detail' => [ - 'component' => 'ShipmentDetail', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager', 'rider', 'audit'], - 'module' => 'shipments', - ], - '/farmer-profile-edit' => [ - 'component' => 'FarmerProfileEdit', - 'loginRequired' => true, - 'module' => 'farmers', - ], - '/verification-dashboard' => [ - 'component' => 'VerificationDashboard', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator'], - 'module' => 'farmers', - ], - '/cooperative-list' => [ - 'component' => 'CooperativeList', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator', 'coop officer', 'coop member'], - 'module' => 'cooperatives', - ], - '/chapter-org-chart' => [ - 'component' => 'ChapterOrgChart', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator', 'coop officer', 'coop member'], - 'module' => 'cooperatives', - ], - '/coop-member-search' => [ - 'component' => 'CoopMemberSearch', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator', 'coop officer'], - 'module' => 'cooperatives', - ], - '/create-coop-user' => [ - 'component' => 'CreateCoopUser', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator', 'coop officer'], - 'module' => 'cooperatives', - ], - '/assign-chapter-officer' => [ - 'component' => 'AssignChapterOfficer', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator', 'coop officer'], - 'module' => 'cooperatives', - ], - '/create-chapter' => [ - 'component' => 'CreateChapter', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator', 'coop officer'], - 'module' => 'cooperatives', - ], - '/register-chapter' => [ - 'component' => 'RegisterChapter', - 'loginRequired' => false, - 'module' => 'cooperatives', - ], - '/create-cooperative' => [ - 'component' => 'CreateCooperative', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'coordinator'], - 'module' => 'cooperatives', - ], + // ── Document Requests + '/barangay/requestdocument' => ['component' => 'Barangay.RequestDocument', 'loginRequired' => true, 'module' => 'certificates'], + '/barangay/managedocumentrequests' => ['component' => 'Barangay.ManageDocumentRequests','loginRequired' => true, 'module' => 'documents'], + '/barangay/documentrequestdetail' => ['component' => 'Barangay.DocumentRequestDetail','loginRequired' => true, 'module' => 'documents'], + '/barangay/managerequesttypes' => ['component' => 'Barangay.ManageRequestTypes', 'loginRequired' => true, 'module' => 'documents'], - '/cooperative-detail' => [ - 'component' => 'CooperativeDetail', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator', 'coop officer', 'coop member'], - 'module' => 'cooperatives', - ], - '/enroll-farmer' => [ - 'component' => 'EnrollFarmer', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator'], - 'module' => 'farmers', - ], - '/cooperative-member-register' => [ - 'component' => 'CooperativeMemberRegister', - 'loginRequired' => true, - 'module' => 'cooperatives', - ], - '/register-coop' => [ - 'component' => 'RegisterCoop', - 'loginRequired' => false, - 'module' => 'cooperatives', - ], - '/user-registration' => [ - 'component' => 'UserRegistration', - 'loginRequired' => false, - ], - '/user-info-edit' => [ - 'component' => 'UserInfoEdit', - 'loginRequired' => true, - ], - '/ultimate-console' => [ - 'component' => 'UltimateConsole', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult'], - ], - '/system-settings' => [ - 'component' => 'SystemSettings', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult'], - ], - '/landing-page-editor' => [ - 'component' => 'LandingPageEditor', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator'], - 'module' => 'landing_pages', - ], - '/accounting-dashboard' => [ - 'component' => 'AccountingDashboard', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'], - 'module' => 'accounting', - 'store_module' => 'accounting_store', - ], - '/manage-accounts' => [ - 'component' => 'ManageAccounts', - 'loginRequired' => true, - 'allowedUserTypes' => ['ult', 'super operator', 'operator', 'store owner', 'store manager'], - 'module' => 'accounting', - 'store_module' => 'accounting_store', - ], + // ── Projects + '/barangay/manageprojects' => ['component' => 'Barangay.ManageProjects', 'loginRequired' => true, 'module' => 'projects'], + + // ── Budget + '/barangay/budgetledger' => ['component' => 'Barangay.BudgetLedger', 'loginRequired' => true, 'module' => 'budget'], ]; @@ -461,7 +159,7 @@ class VueRouteMap $disabledPages = \App\Models\SystemSetting::getValue('disabled_pages', []); if (is_array($disabledPages) && in_array(strtolower((string)$component), array_map('strtolower', $disabledPages))) { // Ultimate accounts can still access to allow fixing settings - if (!$user || $user->acct_type !== UserTypes::ULTIMATE) { + if (!$user || $user->acct_type !== UserTypes::SUPER_ADMIN) { return redirect('/'); } } @@ -601,7 +299,7 @@ class VueRouteMap $disabledPages = \App\Models\SystemSetting::getValue('disabled_pages', []); if (is_array($disabledPages) && in_array(strtolower((string)$vueComponent), array_map('strtolower', $disabledPages))) { // Ultimate accounts can still access to allow fixing settings - if (!$user || $user->acct_type !== UserTypes::ULTIMATE) { + if (!$user || $user->acct_type !== UserTypes::SUPER_ADMIN) { return redirect('/'); } } diff --git a/app/Http/Controllers/UserCreateController.php b/app/Http/Controllers/UserCreateController.php deleted file mode 100644 index 9660c76..0000000 --- a/app/Http/Controllers/UserCreateController.php +++ /dev/null @@ -1,78 +0,0 @@ -user()->acct_type; // Assuming you're using the `acct_type` field for the current user's type - - - - if (!UserPermissions::isActionPermitted($acct_type, UserActions::CreateUser)) { - return response()->json(['error' => 'Permission denied'], 403); - } - - // Step 2: Validate incoming request data - $validator = Validator::make($request->all(), [ - 'name' => 'required|string|max:255', - - - 'email' => 'required|email|unique:users,email', - 'mobile_number' => 'required|string|max:15', - 'password' => 'required|string|min:8', - 'username' => 'nullable|string|unique:users,username', - // Add any other validation rules needed - ]); - - if ($validator->fails()) { - return response()->json(['errors' => $validator->errors()], 422); - } - - if ($acct_type instanceof UserTypes) { - $acct_type = $acct_type->value; - } - - if (!is_string($acct_type) || !$acct_type) { - - } - - // Step 3: Create the new user - $user = User::create([ - 'name' => $request->input('name'), - 'email' => $request->input('email'), - 'mobile_number' => $request->input('mobile_number'), - 'password' => Hash::make($request->input('password')), - 'acct_type' => $acct_type, - 'username' => $request->input('username'), - 'created_by' => auth()->user()->id, // Currently authenticated user - // Add any other fields as needed - ]); - - // Step 4: Handle user-specific logic based on their `acct_type` - $this->handleUserTypeSpecificLogic($acct_type, $user); - - return response()->json([ - 'message' => 'User created successfully', - 'user' => $user - ], 201); - } -} - - diff --git a/app/Http/Controllers/UserManagement/CreateUserControllerUltimate.php b/app/Http/Controllers/UserManagement/CreateUserControllerUltimate.php deleted file mode 100644 index 45447d3..0000000 --- a/app/Http/Controllers/UserManagement/CreateUserControllerUltimate.php +++ /dev/null @@ -1,272 +0,0 @@ -acct_type, UserActions::ViewAllUserTypes)) { - return Response::json(['error' => 'Unauthorized'], 401); - } - - $currentUserType = $currentUser->acct_type; - $allowedTypes = \App\Http\Controllers\Helpers\Permissions\UserTypeService::getAllowedUserTypes($currentUserType); - - $formatted = []; - foreach ($allowedTypes as $case) { - $label = str_replace('_', ' ', ucwords(strtolower($case->name))); - $formatted[] = [$case->value, $label]; - } - - return Response::json($formatted); - } - - public static function listAllUsersforParentSelectHTML(Request $request, $dataResult = false) - { - $currentUser = \Hypervel\Support\Facades\Auth::user(); - - if (!$currentUser) { - return Response::json([], 200); - } - - // Ultimate accounts can see all users - if ($currentUser->acct_type === UserTypes::ULTIMATE) { - $allowedIds = null; - } else { - // Only show current user and their descendants (direct or indirect children) - try { - $descendants = $currentUser->getAllDescendants(); - $allowedIds = $descendants->pluck('id')->toArray(); - $allowedIds[] = $currentUser->id; - } catch (\Throwable $th) { - return Response::json([], 200); - } - } - - $excludeUser = $request->input('exclude_user', null); - $typeFilter = $request->input('type', null); - - $usersQuery = User::select(['id', 'username', 'name', 'fullname', 'mobile_number', 'hashkey', 'acct_type']); - - if ($allowedIds !== null) { - $usersQuery = $usersQuery->whereIn('id', $allowedIds); - } - - // Exclude the specified user if provided - if ($excludeUser) { - $usersQuery = $usersQuery->where('hashkey', '!=', $excludeUser); - } - - if ($typeFilter) { - $types = is_array($typeFilter) ? $typeFilter : [$typeFilter]; - $usersQuery = $usersQuery->whereIn('acct_type', $types); - } - - $users = $usersQuery->get(); - - if (!$dataResult) { - return Response::json($users); - } else { - return $users; - } - } - - public function CreateUser(Request $request) - { - $usertypeString = $request->input('type'); - - if (!is_string($usertypeString) || empty($usertypeString)) { - return Response::json(['error' => 'User type is required'], 400); - } - - $usertypeEnum = UserTypes::tryFrom($usertypeString); - if (!$usertypeEnum) { - return Response::json(['error' => 'Invalid User Type'], 400); - } - - // Map UserTypes to specialized CreateUser UserActions - $action = match ($usertypeEnum) { - UserTypes::ULTIMATE => UserActions::CreateUserUltimate, - UserTypes::SUPER_OPERATOR => UserActions::CreateUserSuperOperator, - UserTypes::OPERATOR => UserActions::CreateUserOperator, - UserTypes::COORDINATOR => UserActions::CreateUserCoordinator, - UserTypes::SUPPLIER_OVERSEER => UserActions::CreateUserSupplierOverseer, - UserTypes::WHOLESALE_BUYER => UserActions::CreateUserWholesaleBuyer, - UserTypes::SUPPLIER => UserActions::CreateUserSupplier, - UserTypes::STORE_OWNER => UserActions::CreateUserStoreOwner, - UserTypes::STORE_MANAGER => UserActions::CreateUserStoreManager, - UserTypes::USER => UserActions::CreateUserUser, - UserTypes::RIDER => UserActions::CreateUserRider, - UserTypes::POS_TERMINAL => UserActions::CreateUserPOSTerminal, - UserTypes::AUDIT => UserActions::CreateUserAudit, - default => UserActions::CreateUser, - }; - - $currentUser = \Hypervel\Support\Facades\Auth::user(); - $targetParentHash = $request->input('parent'); - - if (!$currentUser) { - return Response::json(['error' => 'Unauthorized'], 401); - } - - $currentUserType = $currentUser->acct_type; - if (!($currentUserType instanceof UserTypes)) { - $currentUserType = UserTypes::tryFrom($currentUserType) ?? UserTypes::PUBLIC; - } - - if ($currentUserType !== UserTypes::ULTIMATE) { - // Check the new user's type is in the allowed list for this creator - $allowedTypes = \App\Http\Controllers\Helpers\Permissions\UserTypeService::getAllowedUserTypes($currentUserType); - if (!in_array($usertypeEnum, $allowedTypes)) { - return Response::json(['error' => 'You are not allowed to create this user type.'], 401); - } - - // Check that the chosen parent is the current user or a descendant - if ($targetParentHash) { - $isParentSelfOrDescendant = ($currentUser->hashkey === $targetParentHash) - || UserPermissions::isDescendantOfCurrentUser($targetParentHash); - if (!$isParentSelfOrDescendant) { - return Response::json(['error' => 'Parent user is not in your hierarchy.'], 401); - } - } - } - - - - $mobileRules = ['required', 'string', 'max:20', 'unique:users,mobile_number']; - if (!UserPermissions::isActionPermitted($currentUser->acct_type, UserActions::BypassMobileNumberFormat)) { - $mobileRules[] = 'regex:/^(09|\+639)\d{9}$/'; - } - - try { - $validated = $request->validate([ - 'username' => 'required|string|max:255|unique:users,username', - 'name' => 'required|string|max:255', - 'fullname' => 'nullable|string|max:255', - 'mobile_number' => $mobileRules, - 'password' => 'required|string|min:6', - 'nickname' => 'nullable|string|max:255', - 'parent' => 'required|string', - 'type' => 'required|string', - ]); - } catch (\Hypervel\Validation\ValidationException $e) { - return Response::json(['errors' => $e->errors()], 422); - } - - $parentUser = User::where('hashkey', $validated['parent'])->first(); - - if (!$parentUser) { - return Response::json(['error' => 'Parent user not found'], 404); - } - - $parent = $parentUser->id; - - - $user = new User(); - $user->username = $validated['username']; - $user->name = $validated['name']; - $user->fullname = $validated['fullname'] ?? null; - $user->mobile_number = $validated['mobile_number']; - $user->password = Hash::make($validated['password']); - $user->nickname = $validated['nickname'] ?? null; - $user->parentuid = $parent; - $user->acct_type = $validated['type']; - $user->active = true; - $user->save(); - - return Response::json(['success' => true, 'hashkey' => $user->hashkey, 'message' => 'User created successfully'], 201); - - - } - - - public function checkIfUserMobileNumberExists(Request $request) - { - $request->validate([ - 'mobile_number' => 'required|string', - ]); - $mobileNumber = $request->input('mobile_number'); - $userExists = User::where('mobile_number', $mobileNumber)->exists(); - return Response::json(['exists' => $userExists]); - } - - public function checkIfUsernameExists(Request $request) - { - $request->validate([ - 'username' => 'required|string', - ]); - $username = $request->input('username'); - $userExists = User::where('username', $username)->exists(); - return Response::json(['exists' => $userExists]); - } - - public function publicRegisterUser(Request $request) - { - try { - $validated = $request->validate([ - 'name' => 'required|string|max:255', - 'mobile_number' => 'required|string|max:20|unique:users,mobile_number|regex:/^(09|\+639)\d{9}$/', - 'password' => 'required|string|min:6', - 'nickname' => 'nullable|string|max:255', - ]); - } catch (\Hypervel\Validation\ValidationException $e) { - return Response::json(['success' => false, 'errors' => $e->errors()], 422); - } - - $parent = User::where('acct_type', UserTypes::ULTIMATE->value)->orderBy('id')->first(); - - if (!$parent) { - $parent = User::where('acct_type', UserTypes::COORDINATOR->value)->orderBy('id')->first(); - } - - if (!$parent) { - $parent = User::orderBy('id')->first(); - } - - if (!$parent) { - return Response::json(['success' => false, 'message' => 'No valid parent user found'], 500); - } - - $user = new User(); - $user->name = $validated['name']; - $user->mobile_number = $validated['mobile_number']; - $user->password = Hash::make($validated['password']); - $user->nickname = $validated['nickname'] ?? null; - $user->parentuid = $parent->id; - $user->acct_type = 'user'; - $user->active = true; - $user->save(); - - return Response::json(['success' => true, 'hashkey' => $user->hashkey, 'message' => 'Account created successfully. Please log in.'], 201); - } - - public function publicCheckMobileNumber(Request $request) - { - $request->validate([ - 'mobile_number' => 'required|string', - ]); - - return Response::json(['exists' => User::where('mobile_number', $request->input('mobile_number'))->exists()]); - } - - - -} - diff --git a/app/Http/Controllers/UserManagement/UserAdditionalDetailsController.php b/app/Http/Controllers/UserManagement/UserAdditionalDetailsController.php deleted file mode 100644 index 938b13c..0000000 --- a/app/Http/Controllers/UserManagement/UserAdditionalDetailsController.php +++ /dev/null @@ -1,129 +0,0 @@ -json([ - 'success' => true, - 'data' => [ - 'settings' => $user->settings, - 'details' => $user->details, - ] - ]); - } - - public function updateCooperatives(Request $request) - { - $user = Auth::user(); - if (!$user) { - return ResponseHelper::returnUnauthorized(); - } - - $cooperativeHash = $request->input('cooperative_hash'); - $action = $request->input('action', 'add'); // 'add' or 'remove' - - if (!$cooperativeHash) { - return ResponseHelper::returnIncorrectDetails(); - } - - $settings = $user->settings ?? []; - $cooperatives = $settings['cooperatives'] ?? []; - - if ($action === 'add') { - if (!in_array($cooperativeHash, $cooperatives)) { - $cooperatives[] = $cooperativeHash; - } - } else { - $cooperatives = array_values(array_filter($cooperatives, fn($h) => $h !== $cooperativeHash)); - } - - $settings['cooperatives'] = $cooperatives; - $user->settings = $settings; - - if ($user->save()) { - return response()->json([ - 'success' => true, - 'message' => 'Cooperatives updated successfully', - 'data' => $cooperatives - ]); - } - - return ResponseHelper::returnError('Failed to update cooperatives'); - } - - public function getUserCooperatives(Request $request) - { - $userHash = $request->input('user_hash'); - - if ($userHash) { - $targetUser = User::where('hashkey', $userHash)->first(); - if (!$targetUser) { - return ResponseHelper::returnError('User not found', 404); - } - - // Authorization check - if (!UserPermissions::isActionPermitted($targetUser->acct_type, UserActions::ViewUserInfo)) { - return ResponseHelper::returnUnauthorized(); - } - - $user = $targetUser; - } else { - $user = Auth::user(); - } - - if (!$user) { - return ResponseHelper::returnUnauthorized(); - } - - $cooperativeHashes = $user->settings['cooperatives'] ?? []; - if (empty($cooperativeHashes)) { - return response()->json(['success' => true, 'data' => []]); - } - - $cooperatives = Organization::whereIn('hashkey', $cooperativeHashes)->get(); - - return response()->json([ - 'success' => true, - 'data' => $cooperatives - ]); - } - - public function searchUsersByCooperative(Request $request) - { - if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewUserInfo)) { - return ResponseHelper::returnUnauthorized(); - } - - $cooperativeHash = $request->input('cooperative_hash'); - if (!$cooperativeHash) { - return ResponseHelper::returnIncorrectDetails(); - } - - // Search in the JSON field 'settings' for cooperatives array containing the hash - $users = User::where('settings->cooperatives', 'like', '%' . $cooperativeHash . '%')->get(); - - return response()->json([ - 'success' => true, - 'data' => $users - ]); - } -} diff --git a/app/Http/Controllers/UserPages/UltimateUserController.php b/app/Http/Controllers/UserPages/UltimateUserController.php deleted file mode 100644 index 5cb49e8..0000000 --- a/app/Http/Controllers/UserPages/UltimateUserController.php +++ /dev/null @@ -1,14 +0,0 @@ -acct_type->value === UserTypes::ULTIMATE->value) { + if (Auth::check() && Auth::user()->acct_type->value === UserTypes::SUPER_ADMIN->value) { return response("View for page '{$pagename}' and user type '{$userType}' not found.", 404); } else { return abort(404, 'Page not found.'); @@ -431,7 +431,7 @@ class viewHelperController } $user = Auth::user(); - return isset($user->acct_type) && $user->acct_type->value === UserTypes::ULTIMATE->value; + return isset($user->acct_type) && $user->acct_type->value === UserTypes::SUPER_ADMIN->value; } diff --git a/app/Http/Middleware/CheckMaintenanceMode.php b/app/Http/Middleware/CheckMaintenanceMode.php index ff3c5da..eaa83df 100644 --- a/app/Http/Middleware/CheckMaintenanceMode.php +++ b/app/Http/Middleware/CheckMaintenanceMode.php @@ -25,7 +25,7 @@ class CheckMaintenanceMode $user = Auth::user(); // Allow Ultimate users to bypass maintenance mode - if (!$user || $user->acct_type !== UserTypes::ULTIMATE) { + if (!$user || $user->acct_type !== UserTypes::SUPER_ADMIN) { // Return 503 Service Unavailable return ResponseHelper::returnError('System is currently under maintenance. Transactions are temporarily disabled. Please try again later.', 503); } diff --git a/app/Http/Middleware/EnsureUserIsUltimate.php b/app/Http/Middleware/EnsureUserIsUltimate.php index ac37e77..16715bd 100644 --- a/app/Http/Middleware/EnsureUserIsUltimate.php +++ b/app/Http/Middleware/EnsureUserIsUltimate.php @@ -23,7 +23,7 @@ class EnsureUserIsUltimate extends Middleware { $user = Auth::user(); - if (!$user || $user->acct_type !== UserTypes::ULTIMATE) { + if (!$user || $user->acct_type !== UserTypes::SUPER_ADMIN) { throw new UnauthorizedHttpException('', 'Unauthorized: Only ultimate users allowed.'); } diff --git a/app/Models/Accounting/Account.php b/app/Models/Accounting/Account.php deleted file mode 100644 index 7363a59..0000000 --- a/app/Models/Accounting/Account.php +++ /dev/null @@ -1,57 +0,0 @@ - 'boolean', - ]; - - public function parent() - { - return $this->belongsTo(self::class, 'parent_id'); - } - - public function children() - { - return $this->hasMany(self::class, 'parent_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } - - public function transactions() - { - return $this->hasMany(AccountTransaction::class, 'account_id'); - } -} diff --git a/app/Models/Accounting/AccountTransaction.php b/app/Models/Accounting/AccountTransaction.php deleted file mode 100644 index 0fcc461..0000000 --- a/app/Models/Accounting/AccountTransaction.php +++ /dev/null @@ -1,49 +0,0 @@ - 'json', - 'amount' => 'decimal:2', - 'transaction_date' => 'datetime', - ]; - - public function account() - { - return $this->belongsTo(Account::class, 'account_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Accounting/MemberLedger.php b/app/Models/Accounting/MemberLedger.php deleted file mode 100644 index b41e512..0000000 --- a/app/Models/Accounting/MemberLedger.php +++ /dev/null @@ -1,55 +0,0 @@ - 'decimal:2', - 'balance_after' => 'decimal:2', - 'is_active' => 'boolean', - ]; - - public function user() - { - return $this->belongsTo(User::class, 'user_id'); - } - - public function organization() - { - return $this->belongsTo(Organization::class, 'organization_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Barangay/BarangayBudget.php b/app/Models/Barangay/BarangayBudget.php new file mode 100644 index 0000000..600eedb --- /dev/null +++ b/app/Models/Barangay/BarangayBudget.php @@ -0,0 +1,42 @@ + 'decimal:2', + 'date' => 'date', + ]; + + public function encodedBy() + { + return $this->belongsTo(\App\Models\User::class, 'encoded_by'); + } + + public function scopeIncome($query) + { + return $query->where('category', 'INCOME'); + } + + public function scopeExpense($query) + { + return $query->where('category', 'EXPENSE'); + } + + public function scopeByYear($query, int $year) + { + return $query->where('fiscal_year', $year); + } +} diff --git a/app/Models/Barangay/BarangayProject.php b/app/Models/Barangay/BarangayProject.php new file mode 100644 index 0000000..4963c10 --- /dev/null +++ b/app/Models/Barangay/BarangayProject.php @@ -0,0 +1,44 @@ + 'decimal:2', + 'start_date' => 'date', + 'end_date' => 'date', + 'beneficiaries_count' => 'integer', + ]; + + public function createdBy() + { + return $this->belongsTo(\App\Models\User::class, 'created_by'); + } + + public function scopeActive($query) + { + return $query->whereNotIn('status', ['CANCELLED', 'COMPLETED']); + } + + public function scopeByType($query, string $type) + { + return $query->where('type', $type); + } +} diff --git a/app/Models/Barangay/Blotter.php b/app/Models/Barangay/Blotter.php new file mode 100644 index 0000000..35692f7 --- /dev/null +++ b/app/Models/Barangay/Blotter.php @@ -0,0 +1,70 @@ + 'date', + 'complaint_date' => 'date', + 'is_active' => 'boolean', + 'status' => BlotterStatus::class, + ]; + + public function complainant() + { + return $this->belongsTo(\App\Models\User::class, 'complainant_user_id'); + } + + public function respondent() + { + return $this->belongsTo(\App\Models\User::class, 'respondent_user_id'); + } + + public function assignedOfficer() + { + return $this->belongsTo(\App\Models\User::class, 'assigned_officer_id'); + } + + public function hearings() + { + return $this->hasMany(BlotterHearing::class, 'blotter_id'); + } + + public function nextHearing() + { + return $this->hearings()->where('status', 'SCHEDULED')->orderBy('hearing_date')->first(); + } + + public function scopeActive($query) + { + return $query->where('is_active', true); + } + + public static function generateBlotterNo(): string + { + $year = date('Y'); + $count = static::whereYear('created_at', $year)->count() + 1; + return sprintf('BLT-%s-%04d', $year, $count); + } +} diff --git a/app/Models/Barangay/BlotterHearing.php b/app/Models/Barangay/BlotterHearing.php new file mode 100644 index 0000000..9188d7e --- /dev/null +++ b/app/Models/Barangay/BlotterHearing.php @@ -0,0 +1,32 @@ + 'datetime', + 'next_hearing_date' => 'datetime', + ]; + + public function blotter() + { + return $this->belongsTo(Blotter::class, 'blotter_id'); + } + + public function officer() + { + return $this->belongsTo(\App\Models\User::class, 'officer_id'); + } +} diff --git a/app/Models/Barangay/DocumentRequest.php b/app/Models/Barangay/DocumentRequest.php new file mode 100644 index 0000000..a842de3 --- /dev/null +++ b/app/Models/Barangay/DocumentRequest.php @@ -0,0 +1,78 @@ + 'decimal:2', + 'status' => DocumentStatus::class, + 'payment_status' => PaymentStatus::class, + 'claimed_at' => 'datetime', + ]; + + public function requestType() + { + return $this->belongsTo(RequestType::class, 'request_type_id'); + } + + public function resident() + { + return $this->belongsTo(\App\Models\User::class, 'resident_user_id'); + } + + public function processedBy() + { + return $this->belongsTo(\App\Models\User::class, 'processed_by'); + } + + public function requestedBy() + { + return $this->belongsTo(\App\Models\User::class, 'requested_by'); + } + + public function payments() + { + return $this->hasMany(RequestPayment::class, 'request_id'); + } + + public function latestPayment() + { + return $this->payments()->latest()->first(); + } + + public static function generateRequestNo(): string + { + $year = date('Y'); + $count = static::whereYear('created_at', $year)->count() + 1; + return sprintf('REQ-%s-%05d', $year, $count); + } + + public function scopePending($query) + { + return $query->whereIn('status', [DocumentStatus::DRAFT, DocumentStatus::PENDING_PAYMENT]); + } + + public function scopeForProcessing($query) + { + return $query->where('status', DocumentStatus::PAID); + } +} diff --git a/app/Models/Barangay/Household.php b/app/Models/Barangay/Household.php new file mode 100644 index 0000000..915b7d6 --- /dev/null +++ b/app/Models/Barangay/Household.php @@ -0,0 +1,51 @@ + 'decimal:2', + 'has_electricity' => 'boolean', + 'has_water' => 'boolean', + 'is_active' => 'boolean', + 'member_count' => 'integer', + ]; + + public function head() + { + return $this->belongsTo(Resident::class, 'head_resident_id'); + } + + public function members() + { + return $this->hasMany(HouseholdMember::class, 'household_id'); + } + + public function activeMembers() + { + return $this->members()->where('is_active', true); + } + + public function scopeActive($query) + { + return $query->where('is_active', true); + } +} diff --git a/app/Models/Barangay/HouseholdMember.php b/app/Models/Barangay/HouseholdMember.php new file mode 100644 index 0000000..d64db2a --- /dev/null +++ b/app/Models/Barangay/HouseholdMember.php @@ -0,0 +1,30 @@ + 'boolean', + ]; + + public function household() + { + return $this->belongsTo(Household::class, 'household_id'); + } + + public function resident() + { + return $this->belongsTo(Resident::class, 'resident_id'); + } +} diff --git a/app/Models/Barangay/RequestPayment.php b/app/Models/Barangay/RequestPayment.php new file mode 100644 index 0000000..3fb57ae --- /dev/null +++ b/app/Models/Barangay/RequestPayment.php @@ -0,0 +1,32 @@ + 'decimal:2', + 'paid_at' => 'datetime', + ]; + + public function documentRequest() + { + return $this->belongsTo(DocumentRequest::class, 'request_id'); + } + + public function verifiedBy() + { + return $this->belongsTo(\App\Models\User::class, 'verified_by'); + } +} diff --git a/app/Models/Barangay/RequestType.php b/app/Models/Barangay/RequestType.php new file mode 100644 index 0000000..a0d53b9 --- /dev/null +++ b/app/Models/Barangay/RequestType.php @@ -0,0 +1,34 @@ + 'decimal:2', + 'processing_days' => 'integer', + 'is_active' => 'boolean', + 'requires_clearance' => 'boolean', + ]; + + public function documentRequests() + { + return $this->hasMany(DocumentRequest::class, 'request_type_id'); + } + + public function scopeActive($query) + { + return $query->where('is_active', true); + } +} diff --git a/app/Models/Barangay/Resident.php b/app/Models/Barangay/Resident.php new file mode 100644 index 0000000..a9fa939 --- /dev/null +++ b/app/Models/Barangay/Resident.php @@ -0,0 +1,67 @@ + 'date', + 'monthly_income' => 'decimal:2', + 'voter_status' => 'boolean', + 'head_of_household' => 'boolean', + 'is_active' => 'boolean', + ]; + + public function user() + { + return $this->belongsTo(\App\Models\User::class, 'user_id'); + } + + public function household() + { + return $this->hasOne(Household::class, 'head_resident_id'); + } + + public function householdMemberships() + { + return $this->hasMany(HouseholdMember::class, 'resident_id'); + } + + public function documentRequests() + { + return $this->hasMany(DocumentRequest::class, 'resident_user_id', 'user_id'); + } + + public function scopeActive($query) + { + return $query->where('is_active', true); + } + + public function getFullnameAttribute(): string + { + $parts = array_filter([$this->firstname, $this->middlename, $this->lastname]); + $name = implode(' ', $parts); + if ($this->suffix) $name .= ', ' . $this->suffix; + return $name; + } +} diff --git a/app/Models/Chapter.php b/app/Models/Chapter.php index cccdb67..241f90b 100644 --- a/app/Models/Chapter.php +++ b/app/Models/Chapter.php @@ -4,14 +4,12 @@ declare(strict_types=1); namespace App\Models; -use App\Models\Market\UserInfo; - class Chapter extends Model { protected ?string $table = 'chapters'; protected array $fillable = [ - 'hashkey', 'name', 'cooperative_id', 'level', 'parent_id', 'location_key', + 'hashkey', 'name', 'level', 'parent_id', 'location_key', 'lat', 'lng', 'is_active', 'created_by', 'updated_by', ]; @@ -26,11 +24,6 @@ class Chapter extends Model return $this->belongsTo(Chapter::class, 'parent_id'); } - public function cooperative() - { - return $this->belongsTo(\App\Models\Market\Organization::class, 'cooperative_id'); - } - public function children() { return $this->hasMany(Chapter::class, 'parent_id'); @@ -51,61 +44,19 @@ class Chapter extends Model return $this->activeMembers()->whereNotNull('position'); } - /** - * Find or create a chapter by level + location_key (normalized address field). - */ public static function findOrCreateByLocation(string $level, string $locationKey, ?int $parentId = null): self { $key = strtolower(trim($locationKey)); return static::firstOrCreate( ['level' => $level, 'location_key' => $key], [ - 'hashkey' => \Ramsey\Uuid\Uuid::uuid4()->toString(), - 'name' => ucwords(strtolower($locationKey)), - 'level' => $level, - 'location_key'=> $key, - 'parent_id' => $parentId, - 'is_active' => true, + 'hashkey' => hash('sha256', uniqid((string) now(), true)), + 'name' => ucwords(strtolower($locationKey)), + 'level' => $level, + 'location_key' => $key, + 'parent_id' => $parentId, + 'is_active' => true, ] ); } - - /** - * Auto-assign a user to the appropriate chapters based on their UserInfo address. - * Creates chapter records on the fly if they don't exist. - */ - public static function autoAssignUser(int $userId): void - { - $info = UserInfo::where('user_id', $userId)->first(); - if (!$info) { - return; - } - - $national = static::firstOrCreate( - ['level' => 'national', 'location_key' => 'philippines'], - ['hashkey' => \Ramsey\Uuid\Uuid::uuid4()->toString(), 'name' => 'Philippines', 'level' => 'national', 'location_key' => 'philippines', 'is_active' => true] - ); - - ChapterMember::syncAutoAssignment($userId, $national->id); - - if ($info->region) { - $region = static::findOrCreateByLocation('region', $info->region, $national->id); - ChapterMember::syncAutoAssignment($userId, $region->id); - - if ($info->province) { - $province = static::findOrCreateByLocation('province', $info->province, $region->id); - ChapterMember::syncAutoAssignment($userId, $province->id); - - if ($info->city) { - $city = static::findOrCreateByLocation('city', $info->city, $province->id); - ChapterMember::syncAutoAssignment($userId, $city->id); - - if ($info->barangay) { - $barangay = static::findOrCreateByLocation('barangay', $info->barangay, $city->id); - ChapterMember::syncAutoAssignment($userId, $barangay->id); - } - } - } - } - } } diff --git a/app/Models/Generic/TableLog.php b/app/Models/Generic/TableLog.php deleted file mode 100644 index e1aa0fa..0000000 --- a/app/Models/Generic/TableLog.php +++ /dev/null @@ -1,47 +0,0 @@ - 'array', - 'new_data' => 'array', - ]; - - protected array $fillable = [ - 'hashkey', - 'table_name', - 'target_id', - 'original_data', - 'new_data', - 'created_by', - 'updated_by', - ]; - - // Auto-merge accessor - public function get_full_new_row(): array - { - return array_merge($this->original_data ?? [], $this->new_data ?? []); - } - - public function data(){ - return $this->get_full_new_row(); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} \ No newline at end of file diff --git a/app/Models/GlobalTransaction.php b/app/Models/GlobalTransaction.php deleted file mode 100644 index 7916a08..0000000 --- a/app/Models/GlobalTransaction.php +++ /dev/null @@ -1,72 +0,0 @@ - 'decimal:2', - 'type' => ProductTransactionType::class, - 'user_id' => 'integer', - 'product_id' => 'integer', - 'store_id' => 'integer', - 'flow' => TransactionFlow::class, - 'created_by' => 'integer', - 'updated_by' => 'integer', - ]; - - public function user() - { - return $this->belongsTo(User::class, 'user_id'); - } - - public function product() - { - return $this->belongsTo(Product::class, 'product_id'); - } - - public function store() - { - return $this->belongsTo(Store::class, 'store_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Market/Cart.php b/app/Models/Market/Cart.php deleted file mode 100644 index f16781a..0000000 --- a/app/Models/Market/Cart.php +++ /dev/null @@ -1,35 +0,0 @@ - 'boolean', - ]; - - public function items() - { - return $this->hasMany(CartItem::class, 'cart_id'); - } - - public function user() - { - return $this->belongsTo(User::class, 'user_id'); - } -} diff --git a/app/Models/Market/CartItem.php b/app/Models/Market/CartItem.php deleted file mode 100644 index 8f2170d..0000000 --- a/app/Models/Market/CartItem.php +++ /dev/null @@ -1,39 +0,0 @@ - 'boolean', - 'quantity' => 'integer', - 'price' => 'float', - ]; - - public function cart() - { - return $this->belongsTo(Cart::class, 'cart_id'); - } - - public function product() - { - return $this->belongsTo(Product::class, 'product_id'); - } -} diff --git a/app/Models/Market/CooperativeDocument.php b/app/Models/Market/CooperativeDocument.php deleted file mode 100644 index 0fef700..0000000 --- a/app/Models/Market/CooperativeDocument.php +++ /dev/null @@ -1,41 +0,0 @@ -belongsTo(Organization::class, 'organization_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Market/CooperativeMember.php b/app/Models/Market/CooperativeMember.php deleted file mode 100644 index f5ff048..0000000 --- a/app/Models/Market/CooperativeMember.php +++ /dev/null @@ -1,63 +0,0 @@ - 'datetime', - 'is_active' => 'boolean', - 'priority_sector' => 'array', - 'vulnerability_classifications' => 'array', - 'nsrp_skills' => 'array', - 'program_participation' => 'array', - ]; - - public function organization() - { - return $this->belongsTo(Organization::class, 'organization_id'); - } - - public function user() - { - return $this->belongsTo(User::class, 'user_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Market/CooperativeResolution.php b/app/Models/Market/CooperativeResolution.php deleted file mode 100644 index 66bdce1..0000000 --- a/app/Models/Market/CooperativeResolution.php +++ /dev/null @@ -1,51 +0,0 @@ - 'date', - 'is_active' => 'boolean', - ]; - - public function organization() - { - return $this->belongsTo(Organization::class, 'organization_id'); - } - - public function votes() - { - return $this->hasMany(CooperativeVote::class, 'resolution_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Market/CooperativeVote.php b/app/Models/Market/CooperativeVote.php deleted file mode 100644 index 7a6c01a..0000000 --- a/app/Models/Market/CooperativeVote.php +++ /dev/null @@ -1,47 +0,0 @@ - 'boolean', - ]; - - public function resolution() - { - return $this->belongsTo(CooperativeResolution::class, 'resolution_id'); - } - - public function voter() - { - return $this->belongsTo(User::class, 'user_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Market/Courier.php b/app/Models/Market/Courier.php deleted file mode 100644 index 29dc859..0000000 --- a/app/Models/Market/Courier.php +++ /dev/null @@ -1,42 +0,0 @@ - 'boolean', - ]; - - public function shipments() - { - return $this->hasMany(Shipment::class, 'courier_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Market/Customer.php b/app/Models/Market/Customer.php deleted file mode 100644 index d9eaa64..0000000 --- a/app/Models/Market/Customer.php +++ /dev/null @@ -1,52 +0,0 @@ - 'boolean', - ]; - - /** - * Relationships - */ - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } - - public function user() - { - return $this->belongsTo(User::class, 'user_id'); - } - - public function store() - { - return $this->belongsTo(Store::class, 'store_id'); - } -} diff --git a/app/Models/Market/FarmerProfile.php b/app/Models/Market/FarmerProfile.php deleted file mode 100644 index 2b80c39..0000000 --- a/app/Models/Market/FarmerProfile.php +++ /dev/null @@ -1,53 +0,0 @@ - 'array', - 'certification_details' => 'array', - 'is_active' => 'boolean', - ]; - - public function user() - { - return $this->belongsTo(User::class, 'user_id'); - } - - public function organization() - { - return $this->belongsTo(Organization::class, 'organization_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Market/MainOrganization.php b/app/Models/Market/MainOrganization.php deleted file mode 100644 index f03533e..0000000 --- a/app/Models/Market/MainOrganization.php +++ /dev/null @@ -1,44 +0,0 @@ - 'boolean', - 'priority' => 'integer', - 'metadata' => 'array', - ]; - - public function organization() - { - return $this->belongsTo(Organization::class, 'organization_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Market/Organization.php b/app/Models/Market/Organization.php deleted file mode 100644 index 42ebcf1..0000000 --- a/app/Models/Market/Organization.php +++ /dev/null @@ -1,78 +0,0 @@ - 'boolean', - 'registration_date' => 'date', - ]; - - public function members() - { - return $this->hasMany(CooperativeMember::class, 'organization_id'); - } - - public function farmerProfiles() - { - return $this->hasMany(FarmerProfile::class, 'organization_id'); - } - - public function stores() - { - return $this->belongsToMany(Store::class, 'org_str', 'organization_id', 'store_id') - ->withTimestamps(); - } - - public function mainAssignments() - { - return $this->hasMany(MainOrganization::class, 'organization_id'); - } - - public function isMain(?string $role = null): bool - { - $query = $this->mainAssignments()->where('is_active', true); - if ($role !== null) { - $query->where('role', $role); - } - return $query->exists(); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Market/PosAccessKey.php b/app/Models/Market/PosAccessKey.php deleted file mode 100644 index e5f5694..0000000 --- a/app/Models/Market/PosAccessKey.php +++ /dev/null @@ -1,89 +0,0 @@ - 'integer', - 'store_id' => 'integer', - 'created_by' => 'integer', - 'updated_by' => 'integer', - 'is_active' => 'boolean', - 'expires_at' => 'datetime', - ]; - - /** - * Check if this access key is expired. - */ - public function isExpired(): bool - { - return $this->expires_at !== null && $this->expires_at->isPast(); - } - - /** - * Auto-expire: set all expired active keys to inactive. - * Call this before listing or validating keys. - */ - public static function autoExpire(): void - { - self::where('status', 'active') - ->whereNotNull('expires_at') - ->where('expires_at', '<', now()) - ->update(['status' => 'inactive']); - } - - /** - * Relationships - */ - public function store() - { - return $this->belongsTo(Store::class, 'store_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } -} diff --git a/app/Models/Market/PosSession.php b/app/Models/Market/PosSession.php deleted file mode 100644 index adffaae..0000000 --- a/app/Models/Market/PosSession.php +++ /dev/null @@ -1,70 +0,0 @@ - 'boolean', - 'payment_details' => 'array', - 'additionaldata' => 'array', - 'total_amount' => 'integer', - 'received_amount' => 'integer', - 'change_amount' => 'integer', - 'created_by' => 'integer', - 'updated_by' => 'integer', - ]; - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } - - /** - * Relationships - */ - public function transactions() - { - return $this->hasMany(PosTransaction::class, 'pos_session_id'); - } - - public function archives() - { - return $this->hasMany(PosSessionArchive::class, 'pos_session_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function store() - { - return $this->belongsTo(Store::class, 'store_id'); - } -} diff --git a/app/Models/Market/PosSessionArchive.php b/app/Models/Market/PosSessionArchive.php deleted file mode 100644 index c636543..0000000 --- a/app/Models/Market/PosSessionArchive.php +++ /dev/null @@ -1,49 +0,0 @@ - 'array', - 'transactions_snapshot' => 'array', - 'created_by' => 'integer', - 'updated_by' => 'integer', - 'pos_session_id' => 'integer', - ]; - - /** - * Relationships - */ - public function session() - { - return $this->belongsTo(PosSession::class, 'pos_session_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Market/PosTransaction.php b/app/Models/Market/PosTransaction.php deleted file mode 100644 index 001f2a3..0000000 --- a/app/Models/Market/PosTransaction.php +++ /dev/null @@ -1,49 +0,0 @@ - 'boolean', - 'quantity' => 'integer', - 'price_at_sale' => 'integer', - 'discount' => 'integer', - 'total_price' => 'integer', - 'created_by' => 'integer', - 'updated_by' => 'integer', - ]; - - /** - * Relationships - */ - public function session() - { - return $this->belongsTo(PosSession::class, 'pos_session_id'); - } - - public function product() - { - return $this->belongsTo(Product::class, 'product_id'); - } -} diff --git a/app/Models/Market/Product.php b/app/Models/Market/Product.php deleted file mode 100644 index 0be890d..0000000 --- a/app/Models/Market/Product.php +++ /dev/null @@ -1,91 +0,0 @@ - 'integer', - 'sold' => 'integer', - 'price' => 'integer', - 'views' => 'integer', - 'rating' => 'integer', - 'is_active' => 'boolean', - 'photourl' => 'array', - 'reviews' => 'array', - 'specs' => 'array', - ]; - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } - - public function stores() - { - return $this->belongsToMany(Store::class, 'prd_str') - ->withPivot(['available', 'price', 'is_active']) - ->withTimestamps(); - } - - public function owner() - { - return $this->belongsTo(User::class, 'owner_id'); - } - - public function createdFor() - { - return $this->belongsTo(User::class, 'created_for'); - } - -} diff --git a/app/Models/Market/ProductTransaction.php b/app/Models/Market/ProductTransaction.php deleted file mode 100644 index f4cc08e..0000000 --- a/app/Models/Market/ProductTransaction.php +++ /dev/null @@ -1,102 +0,0 @@ - 'integer', - 'price' => 'integer', - 'is_void' => 'boolean', - ]; - - /** - * Relationships. - */ - public function product() - { - return $this->belongsTo(Product::class, 'product_id'); - } - - public function store() - { - return $this->belongsTo(Store::class, 'store_id'); - } - - public function owner() - { - return $this->belongsTo(User::class, 'owner_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } - - public function createdFor() - { - return $this->belongsTo(User::class, 'created_for'); - } - - public function session() - { - return $this->belongsTo(ProductTransactionSession::class, 'transactionsessionhash', 'hashkey'); - } -} diff --git a/app/Models/Market/ProductTransactionSession.php b/app/Models/Market/ProductTransactionSession.php deleted file mode 100644 index c3ebbdc..0000000 --- a/app/Models/Market/ProductTransactionSession.php +++ /dev/null @@ -1,84 +0,0 @@ - 'boolean', - ]; - - /** - * Relationships. - */ - public function transactions() - { - return $this->hasMany(ProductTransaction::class, 'transactionsessionhash', 'hashkey'); - } - - public function store() - { - return $this->belongsTo(Store::class, 'store_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } - - public function createdFor() - { - return $this->belongsTo(User::class, 'created_for'); - } -} diff --git a/app/Models/Market/ProductTransactionSessionArchive.php b/app/Models/Market/ProductTransactionSessionArchive.php deleted file mode 100644 index 2fc43cc..0000000 --- a/app/Models/Market/ProductTransactionSessionArchive.php +++ /dev/null @@ -1,72 +0,0 @@ - 'array', - 'transactions_snapshot' => 'array', - ]; - - /** - * Relationships. - */ - public function transactionSession() - { - return $this->belongsTo(ProductTransactionSession::class, 'transactions_sessions_id'); - } - - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } - - public function createdFor() - { - return $this->belongsTo(User::class, 'created_for'); - } -} diff --git a/app/Models/Market/Shipment.php b/app/Models/Market/Shipment.php deleted file mode 100644 index f505840..0000000 --- a/app/Models/Market/Shipment.php +++ /dev/null @@ -1,69 +0,0 @@ - 'datetime', - 'actual_delivery_date' => 'datetime', - 'shipping_fee' => 'decimal:2', - 'is_active' => 'boolean', - ]; - - public function transaction() - { - return $this->belongsTo(GlobalTransaction::class, 'transaction_id'); - } - - public function store() - { - return $this->belongsTo(Store::class, 'store_id'); - } - - public function customer() - { - return $this->belongsTo(Customer::class, 'customer_id'); - } - - public function courier() - { - return $this->belongsTo(Courier::class, 'courier_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Market/Store.php b/app/Models/Market/Store.php deleted file mode 100644 index 3110b68..0000000 --- a/app/Models/Market/Store.php +++ /dev/null @@ -1,117 +0,0 @@ - 'array', - 'is_active' => 'boolean', - 'store_type'=> 'array', - 'specs' => 'array', - ]; - - /** - * Relationships - */ - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } - - public function createdFor() - { - return $this->belongsTo(User::class, 'created_for'); - } - - public function owner() - { - return $this->belongsTo(User::class, 'owner_id'); - } - - public function manager() - { - return $this->belongsTo(User::class, 'manager_id'); - } - - public function managers() - { - return $this->hasMany(StoreManager::class, 'store_id'); - } - - public function managerUsers() - { - return $this->belongsToMany(User::class, 'store_managers', 'store_id', 'user_id') - ->withPivot(['hashkey', 'created_by', 'updated_by', 'is_active']) - ->withTimestamps(); - } - - // public function products() - // { - // return $this->hasMany(Product::class, 'store_id'); - // } - - public function products() - { - return $this->belongsToMany(Product::class, 'prd_str') - ->withPivot(['available', 'price', 'is_active','sold','logs','reviews','status','remarks','description']) - ->withTimestamps(); - } - - public function cooperatives() - { - return $this->belongsToMany(Organization::class, 'org_str', 'store_id', 'organization_id') - ->where('organizations.type', 'COOPERATIVE') - ->withTimestamps(); - } - - public function transactions() - { - return $this->hasMany(ProductTransaction::class, 'store_id'); - } - - public function transactionSessions() - { - return $this->hasMany(ProductTransactionSession::class, 'store_id'); - } - - -} diff --git a/app/Models/Market/StoreManager.php b/app/Models/Market/StoreManager.php deleted file mode 100644 index 97f2e02..0000000 --- a/app/Models/Market/StoreManager.php +++ /dev/null @@ -1,46 +0,0 @@ - 'boolean', - ]; - - public function store() - { - return $this->belongsTo(Store::class, 'store_id'); - } - - public function user() - { - return $this->belongsTo(User::class, 'user_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Market/UserInfo.php b/app/Models/Market/UserInfo.php deleted file mode 100644 index e76b999..0000000 --- a/app/Models/Market/UserInfo.php +++ /dev/null @@ -1,114 +0,0 @@ - 'json', - 'addresses' => 'json', - 'other_details' => 'json', - 'is_active' => 'boolean', - 'dob' => 'date', - 'monthly_income' => 'float', - 'children_count' => 'integer', - 'dependent_count' => 'integer', - ]; - - /** - * Get the virtual age attribute. - */ - public function getAgeAttribute(): ?int - { - if (!$this->dob) { - return null; - } - return $this->dob->diffInYears(now()); - } - - public function user() - { - return $this->belongsTo(User::class, 'user_id'); - } - - public function emergencyContactUser() - { - return $this->belongsTo(User::class, 'emergency_contact_user_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Property/Property.php b/app/Models/Property/Property.php deleted file mode 100644 index db584fc..0000000 --- a/app/Models/Property/Property.php +++ /dev/null @@ -1,46 +0,0 @@ - 'json', - 'price' => 'decimal:2', - 'is_active' => 'boolean', - ]; - - public function referrals() - { - return $this->hasMany(Referral::class, 'property_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Property/Referral.php b/app/Models/Property/Referral.php deleted file mode 100644 index d172f40..0000000 --- a/app/Models/Property/Referral.php +++ /dev/null @@ -1,57 +0,0 @@ - 'json', - 'is_active' => 'boolean', - ]; - - public function property() - { - return $this->belongsTo(Property::class, 'property_id'); - } - - public function referrer() - { - return $this->belongsTo(User::class, 'referrer_id'); - } - - public function referred() - { - return $this->belongsTo(User::class, 'referred_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Property/ReferralKey.php b/app/Models/Property/ReferralKey.php deleted file mode 100644 index 35060d2..0000000 --- a/app/Models/Property/ReferralKey.php +++ /dev/null @@ -1,41 +0,0 @@ - 'boolean', - ]; - - public function user() - { - return $this->belongsTo(User::class, 'user_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } - - public function updater() - { - return $this->belongsTo(User::class, 'updated_by'); - } -} diff --git a/app/Models/Subscription/Subscription.php b/app/Models/Subscription/Subscription.php deleted file mode 100644 index b19a4be..0000000 --- a/app/Models/Subscription/Subscription.php +++ /dev/null @@ -1,54 +0,0 @@ - 'datetime', - 'expires_at' => 'datetime', - 'additional_details' => 'array', - ]; - - public function user() - { - return $this->belongsTo(User::class, 'user_id'); - } - - public function plan() - { - return $this->belongsTo(SubscriptionPlan::class, 'plan_id'); - } - - public function invoices() - { - return $this->hasMany(SubscriptionInvoice::class, 'subscription_id'); - } - - public function isActive(): bool - { - return $this->status === 'active' && $this->expires_at && $this->expires_at->isFuture(); - } -} diff --git a/app/Models/Subscription/SubscriptionInvoice.php b/app/Models/Subscription/SubscriptionInvoice.php deleted file mode 100644 index b0a4994..0000000 --- a/app/Models/Subscription/SubscriptionInvoice.php +++ /dev/null @@ -1,45 +0,0 @@ - 'float', - 'paid_at' => 'datetime', - 'additional_details' => 'array', - ]; - - public function subscription() - { - return $this->belongsTo(Subscription::class, 'subscription_id'); - } - - public function user() - { - return $this->belongsTo(User::class, 'user_id'); - } -} diff --git a/app/Models/Subscription/SubscriptionPlan.php b/app/Models/Subscription/SubscriptionPlan.php deleted file mode 100644 index e3f8afb..0000000 --- a/app/Models/Subscription/SubscriptionPlan.php +++ /dev/null @@ -1,45 +0,0 @@ - 'boolean', - 'price' => 'float', - 'duration_days' => 'integer', - 'additional_details' => 'array', - ]; - - public function subscriptions() - { - return $this->hasMany(Subscription::class, 'plan_id'); - } - - public function creator() - { - return $this->belongsTo(User::class, 'created_by'); - } -} diff --git a/app/Models/User.php b/app/Models/User.php index 98064a8..46dc491 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -84,14 +84,9 @@ class User extends Authenticatable 'denied_roles' => \App\Casts\UserActionsArrayCast::class , ]; - public function userInfo() + public function resident() { - return $this->hasOne(\App\Models\Market\UserInfo::class, 'user_id'); - } - - public function cooperativeMemberships() - { - return $this->hasMany(\App\Models\Market\CooperativeMember::class, 'user_id'); + return $this->hasOne(\App\Models\Barangay\Resident::class, 'user_id'); } public function creator() @@ -153,17 +148,21 @@ class User extends Authenticatable */ public function isUltimate(): bool { - return $this->acct_type === UserTypes::ULTIMATE; + return $this->acct_type === UserTypes::SUPER_ADMIN; } - public function isSuperOperator(): bool + public function isPunongBarangay(): bool { - return $this->acct_type === UserTypes::SUPER_OPERATOR; + return $this->acct_type === UserTypes::PUNONG_BARANGAY; } - public function isOperator(): bool + public function isBarangayStaff(): bool { - return $this->acct_type === UserTypes::OPERATOR; + return in_array($this->acct_type, [ + UserTypes::PUNONG_BARANGAY, UserTypes::KAGAWAD, UserTypes::SECRETARY, + UserTypes::TREASURER, UserTypes::SK_CHAIRPERSON, UserTypes::SK_COUNCILOR, + UserTypes::TANOD, UserTypes::BHW, UserTypes::DAYCARE_WORKER, UserTypes::STAFF, + ]); } /** diff --git a/app/Services/ActivityService.php b/app/Services/ActivityService.php index bbc2746..78052f8 100644 --- a/app/Services/ActivityService.php +++ b/app/Services/ActivityService.php @@ -6,8 +6,6 @@ namespace App\Services; use App\Models\GlobalTransaction; use App\Models\User; -use App\Models\Market\Store; -use App\Models\Market\Product; use Hypervel\Support\Facades\DB; use Hypervel\Support\Facades\Log; diff --git a/app/Support/AccountingTheme.php b/app/Support/AccountingTheme.php index 184045a..84ccc0f 100644 --- a/app/Support/AccountingTheme.php +++ b/app/Support/AccountingTheme.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Support; -use App\Models\Accounting\Account; use App\Models\SystemSetting; use Hypervel\Support\Facades\Config; use Hypervel\Support\Str; diff --git a/app/Support/HashkeyResolver.php b/app/Support/HashkeyResolver.php index e84b61f..8880a4f 100644 --- a/app/Support/HashkeyResolver.php +++ b/app/Support/HashkeyResolver.php @@ -6,8 +6,7 @@ namespace App\Support; use Hypervel\Codec\Json; use App\Models\User; -use App\Models\Market\Product; -use App\Models\Market\Store; +use App\Models\Barangay\Resident; /** * HashkeyResolver provides a universal resolver for any model by hashkey. @@ -51,25 +50,11 @@ class HashkeyResolver } /** - * Resolve a product by its hashkey. - * - * @param string $hashkey The product's hashkey - * @return mixed|null The resolved Product instance or null if not found + * Resolve a resident by their hashkey. */ - public function resolveProductByHashkey($hashkey) + public function resolveResidentByHashkey($hashkey) { - return $this->resolveByHashkey($hashkey, Product::class); - } - - /** - * Resolve a store by its hashkey. - * - * @param string $hashkey The store's hashkey - * @return mixed|null The resolved Store instance or null if not found - */ - public function resolveStoreByHashkey($hashkey) - { - return $this->resolveByHashkey($hashkey, Store::class); + return $this->resolveByHashkey($hashkey, Resident::class); } /** diff --git a/app/Support/SystemSettingsHelper.php b/app/Support/SystemSettingsHelper.php index 1236cae..a0008c3 100644 --- a/app/Support/SystemSettingsHelper.php +++ b/app/Support/SystemSettingsHelper.php @@ -86,15 +86,11 @@ class SystemSettingsHelper } /** - * Get the main cooperative/organization model (if any). + * Get the barangay name from settings. */ - public static function mainOrganization(): ?\App\Models\Market\Organization + public static function barangayName(): ?string { - $hashkey = static::mainOrganizationHashkey(); - if (!$hashkey) { - return null; - } - return \App\Models\Market\Organization::where('hashkey', $hashkey)->first(); + return static::get('barangay_name'); } /** diff --git a/database/migrations/2025_09_08_132613_create_market_products_stores_table.php b/database/migrations/2025_09_08_132613_create_market_products_stores_table.php deleted file mode 100644 index b9d99bb..0000000 --- a/database/migrations/2025_09_08_132613_create_market_products_stores_table.php +++ /dev/null @@ -1,240 +0,0 @@ -bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - $table->string('storecode', 50)->default(''); - $table->string('name', 150)->nullable(); - $table->text('description')->nullable(); - $table->timestamps(); - $table->json('store_type')->nullable(); - $table->string('status', 50)->nullable(); - $table->unsignedBigInteger('created_by')->nullable(); - $table->foreign('created_by')->references('id')->on('users'); - $table->unsignedBigInteger('updated_by')->nullable(); - $table->foreign('updated_by')->references('id')->on('users'); - $table->unsignedBigInteger('created_for')->nullable(); - $table->foreign('created_for')->references('id')->on('users'); - // $table->foreignId('created_by')->constrained('users')->nullable(); - // $table->foreignId('updated_by')->constrained('users')->nullable(); - // $table->foreignId('created_for')->constrained('users')->nullable(); - $table->string('remarks', 50)->nullable(); - $table->longText('logs')->nullable(); - $table->json('specs')->nullable(); - $table->longText('additionaldata')->nullable(); - // $table->foreignId('owner_id')->constrained('users')->nullable(); - // $table->foreignId('manager_id')->constrained('users')->nullable(); - - $table->unsignedBigInteger('owner_id')->nullable(); - $table->foreign('owner_id')->references('id')->on('users'); - - $table->unsignedBigInteger('manager_id')->nullable(); - $table->foreign('manager_id')->references('id')->on('users'); - - $table->string('category', 50)->nullable(); - $table->string('subcategory', 50)->nullable(); - $table->json('photourl')->nullable(); - $table->text('address')->nullable(); - $table->boolean('is_active')->default(true); - - // Indexes - $table->index('storecode'); - $table->index('name'); - $table->index('status'); - $table->index('created_by'); - $table->index('created_for'); - $table->index('owner_id'); - $table->index('manager_id'); - }); - - Schema::create('prd_items', function (Blueprint $table) { - $table->bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - $table->timestamps(); - // $table->foreignId('created_by')->constrained('users')->nullable(); - // $table->foreignId('updated_by')->constrained('users')->nullable(); - // $table->foreignId('created_for')->constrained('users')->nullable(); - - $table->unsignedBigInteger('created_by')->nullable(); - $table->foreign('created_by')->references('id')->on('users'); - $table->unsignedBigInteger('updated_by')->nullable(); - $table->foreign('updated_by')->references('id')->on('users'); - $table->unsignedBigInteger('created_for')->nullable(); - $table->foreign('created_for')->references('id')->on('users'); - - $table->string('category', 50)->nullable(); - $table->string('subcategory', 50)->nullable(); - $table->longText('logs')->nullable(); - $table->json('specs')->nullable(); - $table->string('product_type')->nullable(); - $table->json('photourl')->nullable(); - $table->integer('available')->nullable(); - $table->integer('sold')->nullable(); - $table->bigInteger('price')->nullable(); - - - // $table->foreignId('store_id')->constrained('stores')->nullable(); - - - // $table->foreignId('owner_id')->constrained('users')->nullable(); - - - $table->unsignedBigInteger('owner_id')->nullable(); - $table->foreign('owner_id')->references('id')->on('users'); - - - $table->boolean('is_active')->default(true); - $table->bigInteger('views')->nullable(); - $table->string('name', 300)->nullable(); - $table->longText('description')->nullable(); - $table->json('reviews')->nullable(); - $table->string('barcode', 50)->nullable(); - $table->string('status', 50)->nullable(); - $table->string('remarks', 150)->nullable(); - $table->string('unitname', 300)->nullable(); - $table->integer('rating')->nullable(); - $table->string('sku', 50)->nullable(); - $table->string('qrcode', 300)->nullable(); - $table->string('shortcode', 50)->nullable(); - $table->string('shortname', 50)->nullable(); - - $table->index('created_by'); - $table->index('updated_by'); - $table->index('created_for'); - // $table->index('store_id'); - $table->index('owner_id'); - $table->index('category'); - $table->index('subcategory'); - - }); - - - // Products Transactions table - Schema::create('prd_trx', function (Blueprint $table) { - $table->bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - $table->timestamps(); - - $table->text('description')->nullable(); - $table->text('notes')->nullable(); - $table->tinyInteger('transaction_type')->default(0); - $table->unsignedBigInteger('created_by')->nullable(); - $table->foreign('created_by')->references('id')->on('users'); - $table->unsignedBigInteger('updated_by')->nullable(); - $table->foreign('updated_by')->references('id')->on('users'); - $table->unsignedBigInteger('created_for')->nullable(); - $table->foreign('created_for')->references('id')->on('users'); - - $table->bigInteger('store_id')->nullable(); - $table->string('transactiontype', 50)->nullable(); - $table->bigInteger('product_id')->nullable(); - $table->longText('transactiondata')->nullable(); - $table->string('subtype', 50)->nullable(); - $table->string('name', 300)->nullable(); - // $table->bigInteger('owner_id')->nullable(); - - $table->unsignedBigInteger('owner_id')->nullable(); - $table->foreign('owner_id')->references('id')->on('users'); - - $table->string('transactionsessionhash', 300)->unique()->nullable(); - $table->bigInteger('quantity')->nullable(); - $table->longText('logs')->nullable(); - $table->string('remarks', 50)->nullable(); - $table->integer('price')->nullable(); - $table->boolean('is_void')->default(false); - $table->bigInteger('last_total_price')->nullable(); - $table->bigInteger('last_total_discount')->nullable(); - - $table->index('created_by'); - $table->index('updated_by'); - $table->index('created_for'); - $table->index('name'); - $table->index('transactionsessionhash'); - }); - - // Products Transactions Sessions table - Schema::create('prd_trx_ses', function (Blueprint $table) { - $table->bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - $table->timestamps(); - $table->string('name', 300)->nullable(); - $table->text('description')->nullable(); - $table->longText('logs')->nullable(); - $table->string('remarks', 300)->nullable(); - - $table->text('notes')->nullable(); - $table->unsignedBigInteger('created_by')->nullable(); - $table->foreign('created_by')->references('id')->on('users'); - $table->unsignedBigInteger('updated_by')->nullable(); - $table->foreign('updated_by')->references('id')->on('users'); - $table->unsignedBigInteger('created_for')->nullable(); - $table->foreign('created_for')->references('id')->on('users'); - - $table->string('subtype', 50)->nullable(); - $table->longText('additionaldata')->nullable(); - $table->string('category', 50)->nullable(); - $table->bigInteger('store_id')->nullable(); - $table->string('status', 50)->nullable(); - $table->boolean('is_void')->default(false); - $table->bigInteger('last_total_price')->nullable(); - $table->bigInteger('last_total_discount')->nullable(); - - - $table->index('hashkey'); - $table->index('created_by'); - $table->index('updated_by'); - $table->index('created_for'); - }); - - Schema::create('prd_trx_ses_arc', function (Blueprint $table) { - $table->bigIncrements('id'); - $table->text('name')->nullable(); - $table->text('description')->nullable(); - $table->json('details')->nullable(); - $table->string('hashkey', 300)->unique(); - - $table->unsignedBigInteger('transactions_sessions_id')->nullable(); - $table->foreign('transactions_sessions_id')->references('id')->on('prd_trx_ses'); - - $table->text('notes')->nullable(); - $table->unsignedBigInteger('created_by')->nullable(); - $table->foreign('created_by')->references('id')->on('users'); - $table->unsignedBigInteger('updated_by')->nullable(); - $table->foreign('updated_by')->references('id')->on('users'); - $table->unsignedBigInteger('created_for')->nullable(); - $table->foreign('created_for')->references('id')->on('users'); - $table->json('transactions_snapshot')->nullable(); - - - $table->index('created_by'); - $table->index('updated_by'); - $table->index('created_for'); - $table->index('hashkey'); - }); - } - - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('prd_trx_ses_arc'); - Schema::dropIfExists('prd_trx_ses'); - Schema::dropIfExists('prd_trx'); - Schema::dropIfExists('prd_items_arc'); - Schema::dropIfExists('prd_items'); - Schema::dropIfExists('str'); - - } -}; diff --git a/database/migrations/2025_10_06_155745_create_product_store_table.php b/database/migrations/2025_10_06_155745_create_product_store_table.php deleted file mode 100644 index 6350b0f..0000000 --- a/database/migrations/2025_10_06_155745_create_product_store_table.php +++ /dev/null @@ -1,44 +0,0 @@ -id(); - - $table->foreignId('product_id')->constrained('prd_items')->onDelete('cascade'); - $table->foreignId('store_id')->constrained('str')->onDelete('cascade'); - - // Optional extra data per store-product relation - $table->integer('available')->nullable(); - $table->bigInteger('price')->nullable(); - $table->bigInteger('sold')->nullable(); - $table->longText('logs')->nullable(); - $table->json('reviews')->nullable(); - $table->string('status', 50)->nullable(); - $table->string('remarks', 150)->nullable(); - $table->longText('description')->nullable(); - - $table->boolean('is_active')->default(true); - - $table->timestamps(); - - $table->unique(['product_id', 'store_id']); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('prd_str'); - } -}; diff --git a/database/migrations/2025_11_25_10949_create_accounting_table.php b/database/migrations/2025_11_25_10949_create_accounting_table.php deleted file mode 100644 index 0d43b8b..0000000 --- a/database/migrations/2025_11_25_10949_create_accounting_table.php +++ /dev/null @@ -1,72 +0,0 @@ -bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - - // Self-referencing foreign key for nested accounts - $table->unsignedBigInteger('parent_id')->nullable(); - $table->foreign('parent_id')->references('id')->on('accounts')->cascadeOnDelete(); - - $table->string('name')->index(); - $table->string('type')->index(); // e.g., 'REVENUE', 'EXPENSE' - $table->string('description')->nullable(); - $table->boolean('is_active')->default(true); - - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - - $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); - $table->foreign('updated_by')->references('id')->on('users')->nullOnDelete(); - - $table->timestamps(); - }); - - /** - * ACCOUNT TRANSACTIONS - */ - Schema::create('account_transactions', function (Blueprint $table) { - $table->bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - - // Link to the lowest-level account - $table->unsignedBigInteger('account_id'); - $table->foreign('account_id')->references('id')->on('accounts')->cascadeOnDelete(); - - $table->string('item')->index()->nullable(); - - $table->unsignedBigInteger('target_id')->nullable()->index(); // optional target (user, vendor) - - $table->decimal('amount', 15, 2)->default(0); - $table->dateTime('transaction_date')->index(); - $table->string('reference')->nullable()->index(); // external reference / invoice no. - - $table->json('additional_details')->nullable(); - - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - - $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); - $table->foreign('updated_by')->references('id')->on('users')->nullOnDelete(); - - $table->timestamps(); - }); - } - - public function down(): void - { - Schema::dropIfExists('account_transactions'); - Schema::dropIfExists('accounts'); - } -}; diff --git a/database/migrations/2026_03_23_000000_create_global_transactions_table.php b/database/migrations/2026_03_23_000000_create_global_transactions_table.php deleted file mode 100644 index 2644fd9..0000000 --- a/database/migrations/2026_03_23_000000_create_global_transactions_table.php +++ /dev/null @@ -1,46 +0,0 @@ -bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - - $table->unsignedBigInteger('user_id')->index(); - $table->decimal('amount', 15, 2)->default(0); - $table->integer('type')->default(0); // ProductTransactionType::UNKNOWN - $table->string('status', 50)->default('completed'); - $table->text('description')->nullable(); - - $table->unsignedBigInteger('product_id')->nullable()->index(); - $table->unsignedBigInteger('store_id')->nullable()->index(); - - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - - $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); - $table->foreign('product_id')->references('id')->on('prd_items')->onDelete('set null'); - $table->foreign('store_id')->references('id')->on('str')->onDelete('set null'); - $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); - $table->foreign('updated_by')->references('id')->on('users')->onDelete('set null'); - - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('global_transactions'); - } -}; diff --git a/database/migrations/2026_03_23_000001_create_pos_tables.php b/database/migrations/2026_03_23_000001_create_pos_tables.php deleted file mode 100644 index b5bfeaf..0000000 --- a/database/migrations/2026_03_23_000001_create_pos_tables.php +++ /dev/null @@ -1,76 +0,0 @@ -bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - $table->string('access_key', 300)->unique(); // Guest access key - $table->unsignedBigInteger('store_id')->index(); - $table->unsignedBigInteger('created_by')->nullable()->index(); - $table->unsignedBigInteger('updated_by')->nullable()->index(); - $table->string('customer_name', 300)->nullable(); - $table->bigInteger('total_amount')->default(0); - $table->bigInteger('received_amount')->default(0); - $table->bigInteger('change_amount')->default(0); - $table->string('payment_method', 50)->nullable(); - $table->json('payment_details')->nullable(); - $table->string('status', 50)->default('active'); // active, completed, voided, pending - $table->boolean('is_void')->default(false); - $table->text('notes')->nullable(); - $table->longText('additionaldata')->nullable(); - $table->timestamps(); - - $table->foreign('created_by')->references('id')->on('users'); - // Assuming a stores table exists, but following legacy pattern where it's bigint - }); - - Schema::create('pos_transactions', function (Blueprint $table) { - $table->bigIncrements('id'); - $table->unsignedBigInteger('pos_session_id')->index(); - $table->unsignedBigInteger('product_id')->index(); - $table->integer('quantity')->default(1); - $table->bigInteger('price_at_sale')->default(0); - $table->bigInteger('discount')->default(0); - $table->bigInteger('total_price')->default(0); - $table->boolean('is_void')->default(false); - $table->string('remarks', 300)->nullable(); - $table->timestamps(); - - $table->foreign('pos_session_id')->references('id')->on('pos_sessions')->onDelete('cascade'); - // product_id references prd_items.id - }); - - Schema::create('pos_sessions_archive', function (Blueprint $table) { - $table->bigIncrements('id'); - $table->unsignedBigInteger('pos_session_id')->index(); - $table->string('hashkey', 300)->index(); - $table->json('session_snapshot')->nullable(); - $table->json('transactions_snapshot')->nullable(); - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - $table->string('remarks', 300)->nullable(); - $table->timestamps(); - - $table->foreign('pos_session_id')->references('id')->on('pos_sessions')->onDelete('cascade'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('pos_sessions_archive'); - Schema::dropIfExists('pos_transactions'); - Schema::dropIfExists('pos_sessions'); - } -}; diff --git a/database/migrations/2026_03_23_000002_create_pos_access_keys_table.php b/database/migrations/2026_03_23_000002_create_pos_access_keys_table.php deleted file mode 100644 index fb4572b..0000000 --- a/database/migrations/2026_03_23_000002_create_pos_access_keys_table.php +++ /dev/null @@ -1,36 +0,0 @@ -bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - $table->string('access_key', 300)->unique(); - $table->unsignedBigInteger('store_id')->index(); - $table->string('name', 255)->nullable(); - $table->string('status', 50)->default('active'); - $table->timestamp('last_used_at')->nullable(); - $table->unsignedBigInteger('created_by')->nullable(); - $table->timestamps(); - - $table->foreign('store_id')->references('id')->on('str')->onDelete('cascade'); - $table->foreign('created_by')->references('id')->on('users'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('pos_access_keys'); - } -}; diff --git a/database/migrations/2026_03_23_000003_add_expiry_to_pos_access_keys_table.php b/database/migrations/2026_03_23_000003_add_expiry_to_pos_access_keys_table.php deleted file mode 100644 index 47d8756..0000000 --- a/database/migrations/2026_03_23_000003_add_expiry_to_pos_access_keys_table.php +++ /dev/null @@ -1,21 +0,0 @@ -timestamp('expires_at')->nullable()->after('status'); - }); - } - - public function down(): void - { - Schema::table('pos_access_keys', function (Blueprint $table) { - $table->dropColumn('expires_at'); - }); - } -}; diff --git a/database/migrations/2026_03_23_000004_add_updated_by_to_pos_tables.php b/database/migrations/2026_03_23_000004_add_updated_by_to_pos_tables.php deleted file mode 100644 index aace44d..0000000 --- a/database/migrations/2026_03_23_000004_add_updated_by_to_pos_tables.php +++ /dev/null @@ -1,39 +0,0 @@ -unsignedBigInteger('updated_by')->nullable()->after('created_by')->index(); - } - }); - - Schema::table('pos_sessions_archive', function (Blueprint $table) { - if (!Schema::hasColumn('pos_sessions_archive', 'updated_by')) { - $table->unsignedBigInteger('updated_by')->nullable()->after('created_by'); - } - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::table('pos_sessions', function (Blueprint $table) { - $table->dropColumn('updated_by'); - }); - - Schema::table('pos_sessions_archive', function (Blueprint $table) { - $table->dropColumn('updated_by'); - }); - } -}; diff --git a/database/migrations/2026_03_23_000005_add_missing_columns_to_pos_transactions.php b/database/migrations/2026_03_23_000005_add_missing_columns_to_pos_transactions.php deleted file mode 100644 index 4ad053f..0000000 --- a/database/migrations/2026_03_23_000005_add_missing_columns_to_pos_transactions.php +++ /dev/null @@ -1,29 +0,0 @@ -string('hashkey', 300)->nullable()->unique()->after('id'); - $table->unsignedBigInteger('created_by')->nullable()->after('remarks')->index(); - $table->unsignedBigInteger('updated_by')->nullable()->after('created_by')->index(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::table('pos_transactions', function (Blueprint $table) { - $table->dropColumn(['hashkey', 'created_by', 'updated_by']); - }); - } -}; diff --git a/database/migrations/2026_03_25_000001_add_updated_by_and_is_active_to_pos_access_keys.php b/database/migrations/2026_03_25_000001_add_updated_by_and_is_active_to_pos_access_keys.php deleted file mode 100644 index 6915449..0000000 --- a/database/migrations/2026_03_25_000001_add_updated_by_and_is_active_to_pos_access_keys.php +++ /dev/null @@ -1,33 +0,0 @@ -unsignedBigInteger('updated_by')->nullable()->after('created_by'); - $table->boolean('is_active')->default(true)->after('status'); - - $table->foreign('updated_by')->references('id')->on('users'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::table('pos_access_keys', function (Blueprint $table) { - $table->dropForeign(['updated_by']); - $table->dropColumn(['updated_by', 'is_active']); - }); - } -}; diff --git a/database/migrations/2026_03_25_000002_add_flow_to_global_transactions_table.php b/database/migrations/2026_03_25_000002_add_flow_to_global_transactions_table.php deleted file mode 100644 index 538fe9b..0000000 --- a/database/migrations/2026_03_25_000002_add_flow_to_global_transactions_table.php +++ /dev/null @@ -1,27 +0,0 @@ -integer('flow')->default(0)->after('type'); // App\Enums\Market\TransactionFlow::NEUTRAL - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::table('global_transactions', function (Blueprint $table) { - $table->dropColumn('flow'); - }); - } -}; diff --git a/database/migrations/2026_03_25_000003_create_customers_table.php b/database/migrations/2026_03_25_000003_create_customers_table.php deleted file mode 100644 index 6410206..0000000 --- a/database/migrations/2026_03_25_000003_create_customers_table.php +++ /dev/null @@ -1,29 +0,0 @@ -bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - $table->string('name', 255); - $table->string('phone', 50)->nullable(); - $table->string('email', 255)->nullable(); - $table->unsignedBigInteger('store_id')->nullable(); - $table->unsignedBigInteger('user_id')->nullable(); - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - $table->boolean('is_active')->default(true); - $table->timestamps(); - }); - } - - public function down(): void - { - Schema::dropIfExists('cst'); - } -}; diff --git a/database/migrations/2026_03_25_000252_remove_unique_constraint_from_pos_sessions_access_key.php b/database/migrations/2026_03_25_000252_remove_unique_constraint_from_pos_sessions_access_key.php deleted file mode 100644 index 2ac5244..0000000 --- a/database/migrations/2026_03_25_000252_remove_unique_constraint_from_pos_sessions_access_key.php +++ /dev/null @@ -1,30 +0,0 @@ -dropUnique('pos_sessions_access_key_unique'); - $table->index('access_key'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::table('pos_sessions', function (Blueprint $table) { - $table->dropIndex(['access_key']); - $table->unique('access_key'); - }); - } -}; diff --git a/database/migrations/2026_03_25_204500_create_property_management_tables.php b/database/migrations/2026_03_25_204500_create_property_management_tables.php deleted file mode 100644 index 7e57a4b..0000000 --- a/database/migrations/2026_03_25_204500_create_property_management_tables.php +++ /dev/null @@ -1,89 +0,0 @@ -bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - - $table->string('name')->index(); - $table->string('location')->nullable(); - $table->decimal('price', 15, 2)->default(0); - $table->string('status')->default('available')->index(); - $table->json('details')->nullable(); - - $table->boolean('is_active')->default(true); - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - - $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); - $table->foreign('updated_by')->references('id')->on('users')->nullOnDelete(); - - $table->timestamps(); - }); - } - - if (!Schema::hasTable('referral_keys')) { - Schema::create('referral_keys', function (Blueprint $table) { - $table->bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - - $table->unsignedBigInteger('user_id')->index(); - $table->string('key')->unique()->index(); - - $table->boolean('is_active')->default(true); - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - - $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete(); - $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); - $table->foreign('updated_by')->references('id')->on('users')->nullOnDelete(); - - $table->timestamps(); - }); - } - - if (!Schema::hasTable('referrals')) { - Schema::create('referrals', function (Blueprint $table) { - $table->bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - - $table->unsignedBigInteger('property_id')->index(); - $table->unsignedBigInteger('referrer_id')->index(); - $table->unsignedBigInteger('referred_id')->nullable()->index(); // Can be null if it's just a lead - $table->string('referred_name')->nullable(); - $table->string('referred_contact')->nullable(); - - $table->string('status')->default('pending')->index(); - $table->json('details')->nullable(); - - $table->boolean('is_active')->default(true); - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - - $table->foreign('property_id')->references('id')->on('properties')->cascadeOnDelete(); - $table->foreign('referrer_id')->references('id')->on('users')->cascadeOnDelete(); - $table->foreign('referred_id')->references('id')->on('users')->nullOnDelete(); - $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); - $table->foreign('updated_by')->references('id')->on('users')->nullOnDelete(); - - $table->timestamps(); - }); - } - } - - public function down(): void - { - Schema::dropIfExists('referrals'); - Schema::dropIfExists('referral_keys'); - Schema::dropIfExists('properties'); - } -}; diff --git a/database/migrations/2026_03_26_000001_create_couriers_table.php b/database/migrations/2026_03_26_000001_create_couriers_table.php deleted file mode 100644 index d7b9738..0000000 --- a/database/migrations/2026_03_26_000001_create_couriers_table.php +++ /dev/null @@ -1,37 +0,0 @@ -id(); - $table->string('hashkey', 300)->unique(); - $table->string('name'); - $table->string('contact_number')->nullable(); - $table->string('type', 50)->default('INTERNAL'); // INTERNAL, EXTERNAL - $table->boolean('is_active')->default(true); - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); - $table->foreign('updated_by')->references('id')->on('users')->onDelete('set null'); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('couriers'); - } -}; diff --git a/database/migrations/2026_03_26_000002_create_shipments_table.php b/database/migrations/2026_03_26_000002_create_shipments_table.php deleted file mode 100644 index bb8b9c2..0000000 --- a/database/migrations/2026_03_26_000002_create_shipments_table.php +++ /dev/null @@ -1,51 +0,0 @@ -id(); - $table->string('hashkey', 300)->unique(); - $table->unsignedBigInteger('transaction_id')->index(); - $table->unsignedBigInteger('store_id')->nullable()->index(); - $table->unsignedBigInteger('customer_id')->nullable()->index(); - $table->unsignedBigInteger('courier_id')->nullable()->index(); - $table->string('tracking_number')->unique()->nullable(); - $table->string('status', 50)->default('PENDING'); // PENDING, PICKED_UP, IN_TRANSIT, DELIVERED, FAILED, RETURNED - $table->text('origin_address')->nullable(); - $table->text('destination_address')->nullable(); - $table->timestamp('estimated_delivery_date')->nullable(); - $table->timestamp('actual_delivery_date')->nullable(); - $table->decimal('shipping_fee', 15, 2)->default(0); - $table->boolean('is_active')->default(true); - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - - $table->foreign('transaction_id')->references('id')->on('global_transactions')->onDelete('cascade'); - $table->foreign('store_id')->references('id')->on('str')->onDelete('set null'); - $table->foreign('customer_id')->references('id')->on('cst')->onDelete('set null'); - $table->foreign('courier_id')->references('id')->on('couriers')->onDelete('set null'); - $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); - $table->foreign('updated_by')->references('id')->on('users')->onDelete('set null'); - - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('shipments'); - } -}; diff --git a/database/migrations/2026_03_26_000003_create_farmer_management_tables.php b/database/migrations/2026_03_26_000003_create_farmer_management_tables.php deleted file mode 100644 index f5148a5..0000000 --- a/database/migrations/2026_03_26_000003_create_farmer_management_tables.php +++ /dev/null @@ -1,60 +0,0 @@ -id(); - $table->string('hashkey', 300)->unique(); - $table->string('name'); - $table->string('type', 50)->default('COOPERATIVE'); // COOPERATIVE, ASSOCIATION, COMPANY - $table->text('address')->nullable(); - $table->boolean('is_active')->default(true); - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); - $table->foreign('updated_by')->references('id')->on('users')->onDelete('set null'); - $table->timestamps(); - }); - - Schema::create('farmer_profiles', function (Blueprint $table) { - $table->id(); - $table->string('hashkey', 300)->unique(); - $table->unsignedBigInteger('user_id')->index(); - $table->unsignedBigInteger('organization_id')->nullable()->index(); - $table->string('farm_name')->nullable(); - $table->text('farm_location')->nullable(); - $table->json('main_crops')->nullable(); - $table->string('verification_status', 50)->default('UNVERIFIED'); // UNVERIFIED, PENDING, VERIFIED, REJECTED - $table->json('certification_details')->nullable(); - $table->boolean('is_active')->default(true); - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - - $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); - $table->foreign('organization_id')->references('id')->on('organizations')->onDelete('set null'); - $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); - $table->foreign('updated_by')->references('id')->on('users')->onDelete('set null'); - - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('farmer_profiles'); - Schema::dropIfExists('organizations'); - } -}; diff --git a/database/migrations/2026_03_26_010001_create_cooperative_members_table.php b/database/migrations/2026_03_26_010001_create_cooperative_members_table.php deleted file mode 100644 index 89c964c..0000000 --- a/database/migrations/2026_03_26_010001_create_cooperative_members_table.php +++ /dev/null @@ -1,42 +0,0 @@ -id(); - $table->string('hashkey', 300)->unique(); - $table->unsignedBigInteger('organization_id'); - $table->unsignedBigInteger('user_id'); - $table->string('role', 50)->default('MEMBER'); // MEMBER, OFFICER, ADMIN - $table->timestamp('joined_at')->nullable(); - $table->boolean('is_active')->default(true); - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - - $table->foreign('organization_id')->references('id')->on('organizations')->onDelete('cascade'); - $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); - $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); - $table->foreign('updated_by')->references('id')->on('users')->onDelete('set null'); - - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('cooperative_members'); - } -}; diff --git a/database/migrations/2026_03_28_000002_create_carts_and_cart_items_tables.php b/database/migrations/2026_03_28_000002_create_carts_and_cart_items_tables.php deleted file mode 100644 index 0658e3f..0000000 --- a/database/migrations/2026_03_28_000002_create_carts_and_cart_items_tables.php +++ /dev/null @@ -1,47 +0,0 @@ -id(); - $table->string('hashkey', 300)->unique(); - $table->foreignId('user_id')->constrained('users')->onDelete('cascade'); - $table->boolean('is_active')->default(true); - $table->bigInteger('created_by')->nullable(); - $table->bigInteger('updated_by')->nullable(); - $table->timestamps(); - }); - - Schema::create('cart_items', function (Blueprint $table) { - $table->id(); - $table->string('hashkey', 300)->unique(); - $table->foreignId('cart_id')->constrained('carts')->onDelete('cascade'); - $table->foreignId('product_id')->constrained('prd_items')->onDelete('cascade'); - $table->integer('quantity')->default(1); - $table->decimal('price', 15, 2); - $table->boolean('is_active')->default(true); - $table->bigInteger('created_by')->nullable(); - $table->bigInteger('updated_by')->nullable(); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('cart_items'); - Schema::dropIfExists('carts'); - } -}; diff --git a/database/migrations/2026_03_28_093000_add_cooperative_details_to_organizations_table.php b/database/migrations/2026_03_28_093000_add_cooperative_details_to_organizations_table.php deleted file mode 100644 index 3fe1112..0000000 --- a/database/migrations/2026_03_28_093000_add_cooperative_details_to_organizations_table.php +++ /dev/null @@ -1,49 +0,0 @@ -string('registration_number')->nullable()->after('address'); - $table->string('cin')->nullable()->after('registration_number'); - $table->string('tin')->nullable()->after('cin'); - $table->string('cooperative_type')->nullable()->after('tin'); // e.g., Multi-purpose, Credit - $table->string('cooperative_category')->nullable()->after('cooperative_type'); // Micro, Small, Medium, Large - $table->date('registration_date')->nullable()->after('cooperative_category'); - $table->string('contact_person')->nullable()->after('registration_date'); - $table->string('contact_number')->nullable()->after('contact_person'); - $table->string('contact_email')->nullable()->after('contact_number'); - $table->string('compliance_status')->default('UNKNOWN')->after('contact_email'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::table('organizations', function (Blueprint $table) { - $table->dropColumn([ - 'registration_number', - 'cin', - 'tin', - 'cooperative_type', - 'cooperative_category', - 'registration_date', - 'contact_person', - 'contact_number', - 'contact_email', - 'compliance_status', - ]); - }); - } -}; diff --git a/database/migrations/2026_03_30_000001_add_compound_index_to_pos_transactions.php b/database/migrations/2026_03_30_000001_add_compound_index_to_pos_transactions.php deleted file mode 100644 index 81fe4ab..0000000 --- a/database/migrations/2026_03_30_000001_add_compound_index_to_pos_transactions.php +++ /dev/null @@ -1,25 +0,0 @@ -index(['pos_session_id', 'product_id'], 'pos_tx_session_product_idx'); - }); - } - - public function down(): void - { - Schema::table('pos_transactions', function (Blueprint $table) { - $table->dropIndex('pos_tx_session_product_idx'); - }); - } -} diff --git a/database/migrations/2026_04_02_000002_expand_cooperative_members_table.php b/database/migrations/2026_04_02_000002_expand_cooperative_members_table.php deleted file mode 100644 index 52580b3..0000000 --- a/database/migrations/2026_04_02_000002_expand_cooperative_members_table.php +++ /dev/null @@ -1,57 +0,0 @@ -string('membership_type')->nullable()->after('role'); - } - if (!Schema::hasColumn('cooperative_members', 'membership_level')) { - $table->string('membership_level')->nullable()->after('membership_type'); - } - if (!Schema::hasColumn('cooperative_members', 'officer_position')) { - $table->string('officer_position')->nullable()->after('membership_level'); - } - if (!Schema::hasColumn('cooperative_members', 'officer_level')) { - $table->string('officer_level')->nullable()->after('officer_position'); - } - if (!Schema::hasColumn('cooperative_members', 'concurrent_position')) { - $table->string('concurrent_position')->nullable()->after('officer_level'); - } - if (!Schema::hasColumn('cooperative_members', 'concurrent_level')) { - $table->string('concurrent_level')->nullable()->after('concurrent_position'); - } - if (!Schema::hasColumn('cooperative_members', 'cooperative_name_alt')) { - $table->string('cooperative_name_alt')->nullable()->after('organization_id'); - } - if (!Schema::hasColumn('cooperative_members', 'cooperative_position')) { - $table->string('cooperative_position')->nullable()->after('cooperative_name_alt'); - } - if (!Schema::hasColumn('cooperative_members', 'year_beginning')) { - $table->string('year_beginning')->nullable()->after('cooperative_position'); - } - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::table('cooperative_members', function (Blueprint $table) { - $table->dropColumn([ - 'membership_type', 'membership_level', 'officer_position', 'officer_level', - 'concurrent_position', 'concurrent_level', 'cooperative_name_alt', 'cooperative_position', 'year_beginning' - ]); - }); - } -}; diff --git a/database/migrations/2026_04_02_191649_create_store_managers_table.php b/database/migrations/2026_04_02_191649_create_store_managers_table.php deleted file mode 100644 index bf41bdd..0000000 --- a/database/migrations/2026_04_02_191649_create_store_managers_table.php +++ /dev/null @@ -1,37 +0,0 @@ -id(); - $table->string('hashkey', 300)->unique(); - $table->unsignedBigInteger('store_id'); - $table->unsignedBigInteger('user_id'); - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - $table->boolean('is_active')->default(true); - $table->timestamps(); - - // Foreign keys - $table->foreign('store_id')->references('id')->on('str')->onDelete('cascade'); - $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('store_managers'); - } -}; diff --git a/database/migrations/2026_04_17_000002_create_groups_table.php b/database/migrations/2026_04_17_000002_create_groups_table.php deleted file mode 100644 index 5d4ad1b..0000000 --- a/database/migrations/2026_04_17_000002_create_groups_table.php +++ /dev/null @@ -1,38 +0,0 @@ -bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - $table->string('name'); - $table->string('type'); // e.g., COOPERATIVE, ASSOCIATION, NGO - $table->text('description')->nullable(); - $table->boolean('is_active')->default(true); - $table->bigInteger('created_by')->nullable(); - $table->bigInteger('updated_by')->nullable(); - $table->timestamps(); - - $table->index('created_by'); - $table->index('updated_by'); - $table->index('type'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('groups'); - } -} diff --git a/database/migrations/2026_04_17_000003_create_group_members_table.php b/database/migrations/2026_04_17_000003_create_group_members_table.php deleted file mode 100644 index 8925c16..0000000 --- a/database/migrations/2026_04_17_000003_create_group_members_table.php +++ /dev/null @@ -1,41 +0,0 @@ -bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - $table->unsignedBigInteger('group_id'); - $table->unsignedBigInteger('user_id'); - $table->string('role')->default('MEMBER'); - $table->string('membership_type')->nullable(); - $table->json('extra_details')->nullable(); - $table->boolean('is_active')->default(true); - $table->bigInteger('created_by')->nullable(); - $table->bigInteger('updated_by')->nullable(); - $table->timestamps(); - - $table->foreign('group_id')->references('id')->on('groups')->onDelete('cascade'); - $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); - $table->index('created_by'); - $table->index('updated_by'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('group_members'); - } -} diff --git a/database/migrations/2026_04_17_194347_create_cooperative_resolutions_table.php b/database/migrations/2026_04_17_194347_create_cooperative_resolutions_table.php deleted file mode 100644 index 0819848..0000000 --- a/database/migrations/2026_04_17_194347_create_cooperative_resolutions_table.php +++ /dev/null @@ -1,37 +0,0 @@ -bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - $table->bigInteger('organization_id')->index(); - $table->string('title'); - $table->text('description')->nullable(); - $table->date('date_approved')->nullable(); - $table->string('document_url')->nullable(); - $table->enum('status', ['PROPOSED', 'APPROVED', 'RESCINDED'])->default('PROPOSED'); - $table->bigInteger('created_by')->nullable(); - $table->bigInteger('updated_by')->nullable(); - $table->boolean('is_active')->default(true); - $table->datetimes(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('cooperative_resolutions'); - } -}; diff --git a/database/migrations/2026_04_17_194348_create_cooperative_votes_table.php b/database/migrations/2026_04_17_194348_create_cooperative_votes_table.php deleted file mode 100644 index 40a0c98..0000000 --- a/database/migrations/2026_04_17_194348_create_cooperative_votes_table.php +++ /dev/null @@ -1,36 +0,0 @@ -bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - $table->bigInteger('resolution_id')->index(); - $table->bigInteger('user_id')->index(); - $table->enum('vote_cast', ['YES', 'NO', 'ABSTAIN'])->default('ABSTAIN'); - $table->bigInteger('created_by')->nullable(); - $table->bigInteger('updated_by')->nullable(); - $table->boolean('is_active')->default(true); - $table->datetimes(); - - $table->unique(['resolution_id', 'user_id']); // Ensure one vote per member per resolution - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('cooperative_votes'); - } -}; diff --git a/database/migrations/2026_04_18_040337_create_cooperative_documents_table.php b/database/migrations/2026_04_18_040337_create_cooperative_documents_table.php deleted file mode 100644 index 02d434c..0000000 --- a/database/migrations/2026_04_18_040337_create_cooperative_documents_table.php +++ /dev/null @@ -1,33 +0,0 @@ -id(); - $table->string('hashkey', 300)->unique(); - $table->foreignId('organization_id')->constrained('organizations')->cascadeOnDelete(); - $table->string('file_hashkey', 300); // References file_list.hashkey - $table->string('document_type')->nullable(); // e.g. 'RESOLUTION', 'BYLAWS', 'FINANCIAL', 'OTHERS' - $table->foreignId('created_by')->nullable()->constrained('users'); - $table->foreignId('updated_by')->nullable()->constrained('users'); - $table->boolean('is_active')->default(true); - $table->timestamps(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('cooperative_documents'); - } -}; diff --git a/database/migrations/2026_04_18_042400_add_versioning_to_cooperative_documents_table.php b/database/migrations/2026_04_18_042400_add_versioning_to_cooperative_documents_table.php deleted file mode 100644 index 8f91fdb..0000000 --- a/database/migrations/2026_04_18_042400_add_versioning_to_cooperative_documents_table.php +++ /dev/null @@ -1,29 +0,0 @@ -string('parent_hashkey', 300)->nullable()->after('hashkey'); - $table->integer('version_number')->default(1)->after('parent_hashkey'); - $table->text('revision_note')->nullable()->after('document_type'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::table('cooperative_documents', function (Blueprint $table) { - $table->dropColumn(['parent_hashkey', 'version_number', 'revision_note']); - }); - } -}; diff --git a/database/migrations/2026_04_18_043210_create_member_ledgers_table.php b/database/migrations/2026_04_18_043210_create_member_ledgers_table.php deleted file mode 100644 index ef66244..0000000 --- a/database/migrations/2026_04_18_043210_create_member_ledgers_table.php +++ /dev/null @@ -1,39 +0,0 @@ -bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - $table->bigInteger('user_id')->index(); - $table->bigInteger('organization_id')->index()->nullable(); // Nullable if it's a platform-wide balance - $table->decimal('amount', 15, 2); - $table->string('transaction_type'); // SHARE_CAPITAL, SAVINGS, DIVIDEND, TOP_UP, WITHDRAWAL, PURCHASE - $table->enum('flow', ['IN', 'OUT']); - $table->decimal('balance_after', 15, 2); - $table->text('description')->nullable(); - $table->string('reference_id')->nullable(); // e.g., global_transaction hashkey - $table->bigInteger('created_by')->nullable(); - $table->bigInteger('updated_by')->nullable(); - $table->boolean('is_active')->default(true); - $table->datetimes(); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::dropIfExists('member_ledgers'); - } -}; diff --git a/database/migrations/2026_04_19_000001_expand_cooperative_members_program_data.php b/database/migrations/2026_04_19_000001_expand_cooperative_members_program_data.php deleted file mode 100644 index 78f48bc..0000000 --- a/database/migrations/2026_04_19_000001_expand_cooperative_members_program_data.php +++ /dev/null @@ -1,57 +0,0 @@ -string('priority_sector')->nullable()->after('year_beginning'); - $table->string('common_bond')->nullable()->after('priority_sector'); // Residential/Institutional/Occupational/Associational - $table->json('vulnerability_classifications')->nullable()->after('common_bond'); // IP, PWD, Senior, Solo Parent, OSY, etc. - - // Government IDs (cooperative context) - $table->string('philsys_id')->nullable()->after('vulnerability_classifications'); - $table->string('sss_number')->nullable()->after('philsys_id'); - $table->string('pagibig_number')->nullable()->after('sss_number'); - - // SLP (Sustainable Livelihood Program) - $table->string('slp_track')->nullable()->after('pagibig_number'); // MD / EF - $table->string('slp_association_name')->nullable()->after('slp_track'); - $table->string('listahanan_id')->nullable()->after('slp_association_name'); - $table->string('fourtps_household_id')->nullable()->after('listahanan_id'); - - // TUPAD (DOLE) - $table->string('tupad_category')->nullable()->after('fourtps_household_id'); // Underemployed/Displaced/etc. - $table->string('tupad_insurance_beneficiary_name')->nullable()->after('tupad_category'); - $table->string('tupad_insurance_beneficiary_relation')->nullable()->after('tupad_insurance_beneficiary_name'); - - // OSEC / NSRP Employment - $table->string('preferred_occupation')->nullable()->after('tupad_insurance_beneficiary_relation'); - $table->json('nsrp_skills')->nullable()->after('preferred_occupation'); // Array of skill strings - $table->string('employment_status')->nullable()->after('nsrp_skills'); // Employed/Underemployed/Unemployed - - // Program flags - $table->json('program_participation')->nullable()->after('employment_status'); // ['SLP','TUPAD','OSEC',...] - }); - } - - public function down(): void - { - Schema::table('cooperative_members', function (Blueprint $table) { - $table->dropColumn([ - 'priority_sector', 'common_bond', 'vulnerability_classifications', - 'philsys_id', 'sss_number', 'pagibig_number', - 'slp_track', 'slp_association_name', 'listahanan_id', 'fourtps_household_id', - 'tupad_category', 'tupad_insurance_beneficiary_name', 'tupad_insurance_beneficiary_relation', - 'preferred_occupation', 'nsrp_skills', 'employment_status', 'program_participation', - ]); - }); - } -} diff --git a/database/migrations/2026_04_19_000002_change_priority_sector_to_json_in_cooperative_members.php b/database/migrations/2026_04_19_000002_change_priority_sector_to_json_in_cooperative_members.php deleted file mode 100644 index 647a0f1..0000000 --- a/database/migrations/2026_04_19_000002_change_priority_sector_to_json_in_cooperative_members.php +++ /dev/null @@ -1,17 +0,0 @@ -bigIncrements('id'); - $table->unsignedBigInteger('organization_id'); - $table->unsignedBigInteger('store_id'); - $table->timestamps(); - - $table->foreign('organization_id')->references('id')->on('organizations')->onDelete('cascade'); - $table->foreign('store_id')->references('id')->on('str')->onDelete('cascade'); - - $table->unique(['organization_id', 'store_id']); - $table->index('organization_id'); - $table->index('store_id'); - }); - } - - public function down(): void - { - Schema::dropIfExists('org_str'); - } -}; diff --git a/database/migrations/2026_05_07_120000_create_main_organizations_table.php b/database/migrations/2026_05_07_120000_create_main_organizations_table.php deleted file mode 100644 index 4ca098b..0000000 --- a/database/migrations/2026_05_07_120000_create_main_organizations_table.php +++ /dev/null @@ -1,32 +0,0 @@ -bigIncrements('id'); - $table->unsignedBigInteger('organization_id'); - $table->string('role')->comment('cooperative, ngo, corporation, sponsor, etc.'); - $table->unsignedSmallInteger('priority')->default(0)->comment('0 = primary; higher values = secondary mains in same role'); - $table->boolean('is_active')->default(true); - $table->json('metadata')->nullable()->comment('display name overrides, branding, tagline'); - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - $table->timestamps(); - - $table->foreign('organization_id')->references('id')->on('organizations')->onDelete('cascade'); - $table->index(['role', 'is_active']); - $table->index('organization_id'); - $table->unique(['role', 'priority', 'is_active'], 'main_orgs_role_priority_active_unique'); - }); - } - - public function down(): void - { - Schema::dropIfExists('main_organizations'); - } -}; diff --git a/database/migrations/2026_05_08_120000_add_theme_stamp_to_accounts.php b/database/migrations/2026_05_08_120000_add_theme_stamp_to_accounts.php deleted file mode 100644 index a55b181..0000000 --- a/database/migrations/2026_05_08_120000_add_theme_stamp_to_accounts.php +++ /dev/null @@ -1,23 +0,0 @@ -string('theme_key')->nullable()->index()->after('description'); - $table->string('theme_account_code')->nullable()->index()->after('theme_key'); - }); - } - - public function down(): void - { - Schema::table('accounts', function (Blueprint $table) { - $table->dropColumn(['theme_key', 'theme_account_code']); - }); - } -}; diff --git a/database/migrations/2026_05_08_120100_add_default_flow_to_accounts.php b/database/migrations/2026_05_08_120100_add_default_flow_to_accounts.php deleted file mode 100644 index 1b97081..0000000 --- a/database/migrations/2026_05_08_120100_add_default_flow_to_accounts.php +++ /dev/null @@ -1,31 +0,0 @@ -string('default_flow', 16)->nullable()->index()->after('type'); - }); - - // Backfill existing rows from the old hardcoded rule so behavior is unchanged. - DB::table('accounts') - ->whereIn('type', ['Revenue', 'REVENUE', 'Liability', 'LIABILITY']) - ->update(['default_flow' => 'INCOME']); - DB::table('accounts') - ->whereNull('default_flow') - ->update(['default_flow' => 'EXPENSE']); - } - - public function down(): void - { - Schema::table('accounts', function (Blueprint $table) { - $table->dropColumn('default_flow'); - }); - } -}; diff --git a/database/migrations/2026_05_22_000001_add_store_scope_to_accounting.php b/database/migrations/2026_05_22_000001_add_store_scope_to_accounting.php deleted file mode 100644 index 02f19d2..0000000 --- a/database/migrations/2026_05_22_000001_add_store_scope_to_accounting.php +++ /dev/null @@ -1,35 +0,0 @@ -unsignedBigInteger('store_id')->nullable()->index()->after('parent_id'); - $table->foreign('store_id')->references('id')->on('str')->nullOnDelete(); - }); - - // Add missing columns referenced by AccountingController / AccountTransaction model - Schema::table('account_transactions', function (Blueprint $table) { - $table->string('flow', 16)->nullable()->index()->after('amount'); - $table->string('notes', 500)->nullable()->after('flow'); - }); - } - - public function down(): void - { - Schema::table('account_transactions', function (Blueprint $table) { - $table->dropColumn(['flow', 'notes']); - }); - - Schema::table('accounts', function (Blueprint $table) { - $table->dropForeign(['store_id']); - $table->dropColumn('store_id'); - }); - } -}; diff --git a/database/migrations/2026_05_24_000001_create_subscriptions_tables.php b/database/migrations/2026_05_24_000001_create_subscriptions_tables.php deleted file mode 100644 index eba9dc0..0000000 --- a/database/migrations/2026_05_24_000001_create_subscriptions_tables.php +++ /dev/null @@ -1,98 +0,0 @@ -bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - - $table->string('name')->index(); - $table->string('description')->nullable(); - $table->decimal('price', 15, 2)->default(0); - $table->unsignedInteger('duration_days')->default(30); - // 'restrict' | 'warn' | 'auto_deduct' - $table->string('expiry_action')->default('warn'); - $table->boolean('active')->default(true)->index(); - $table->json('additional_details')->nullable(); - - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); - $table->foreign('updated_by')->references('id')->on('users')->nullOnDelete(); - - $table->timestamps(); - }); - - - Schema::create('subscriptions', function (Blueprint $table) { - $table->bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - - $table->unsignedBigInteger('user_id')->index(); - $table->unsignedBigInteger('plan_id'); - $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete(); - $table->foreign('plan_id')->references('id')->on('subscription_plans')->cascadeOnDelete(); - - // 'active' | 'expired' | 'cancelled' | 'pending' - $table->string('status')->default('pending')->index(); - $table->dateTime('starts_at')->nullable(); - $table->dateTime('expires_at')->nullable()->index(); - - // 'wallet' | 'gcash' | 'paymaya' — extensible for future gateways - $table->string('payment_method')->default('wallet'); - $table->json('additional_details')->nullable(); - - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); - $table->foreign('updated_by')->references('id')->on('users')->nullOnDelete(); - - $table->timestamps(); - }); - - - Schema::create('subscription_invoices', function (Blueprint $table) { - $table->bigIncrements('id'); - $table->string('hashkey', 300)->unique(); - - $table->unsignedBigInteger('subscription_id'); - $table->unsignedBigInteger('user_id')->index(); - $table->foreign('subscription_id')->references('id')->on('subscriptions')->cascadeOnDelete(); - $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete(); - - $table->decimal('amount', 15, 2)->default(0); - // 'pending' | 'paid' | 'failed' - $table->string('status')->default('pending')->index(); - $table->dateTime('paid_at')->nullable(); - - // 'wallet' | 'gcash' | 'paymaya' - $table->string('payment_method')->default('wallet'); - // external gateway transaction ID, QR reference, etc. - $table->string('payment_reference')->nullable()->index(); - - $table->json('additional_details')->nullable(); - - $table->unsignedBigInteger('created_by')->nullable(); - $table->unsignedBigInteger('updated_by')->nullable(); - $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); - $table->foreign('updated_by')->references('id')->on('users')->nullOnDelete(); - - $table->timestamps(); - }); - } - - public function down(): void - { - Schema::dropIfExists('subscription_invoices'); - Schema::dropIfExists('subscriptions'); - Schema::dropIfExists('subscription_plans'); - } -}; diff --git a/database/migrations/2026_05_24_100001_add_default_org_type_system_setting.php b/database/migrations/2026_05_24_100001_add_default_org_type_system_setting.php deleted file mode 100644 index 2bb11e4..0000000 --- a/database/migrations/2026_05_24_100001_add_default_org_type_system_setting.php +++ /dev/null @@ -1,34 +0,0 @@ -where('key', 'default_org_type')->exists(); - if (!$exists) { - \DB::table('system_settings')->insert([ - 'hashkey' => (string) Str::uuid() . Str::random(100), - 'key' => 'default_org_type', - 'value' => 'COOPERATIVE', - 'type' => 'select', - 'options' => json_encode(['COOPERATIVE', 'NGO', 'CORPORATION']), - 'group' => 'general', - 'label' => 'Default Organization Type', - 'description' => 'Pre-selected type when creating a new organization. Options: Cooperative, NGO, Corporation/Company.', - 'created_at' => now(), - 'updated_at' => now(), - ]); - } - } - - public function down(): void - { - \DB::table('system_settings')->where('key', 'default_org_type')->delete(); - } -} diff --git a/database/migrations/2026_05_26_000001_add_bible_verse_system_settings.php b/database/migrations/2026_05_26_000001_add_bible_verse_system_settings.php deleted file mode 100644 index 62f2af5..0000000 --- a/database/migrations/2026_05_26_000001_add_bible_verse_system_settings.php +++ /dev/null @@ -1,45 +0,0 @@ -where('key', 'bible_verse_text')->exists()) { - \DB::table('system_settings')->insert([ - 'hashkey' => (string) Str::uuid() . Str::random(100), - 'key' => 'bible_verse_text', - 'value' => '', - 'type' => 'textarea', - 'group' => 'content', - 'label' => 'Bible Verse of the Day', - 'description' => 'The verse text displayed on the homepage for all users. Leave empty to hide.', - 'created_at' => now(), - 'updated_at' => now(), - ]); - } - - if (!\DB::table('system_settings')->where('key', 'bible_verse_reference')->exists()) { - \DB::table('system_settings')->insert([ - 'hashkey' => (string) Str::uuid() . Str::random(100), - 'key' => 'bible_verse_reference', - 'value' => '', - 'type' => 'text', - 'group' => 'content', - 'label' => 'Bible Verse Reference', - 'description' => 'The book, chapter, and verse (e.g. "John 3:16 NIV").', - 'created_at' => now(), - 'updated_at' => now(), - ]); - } - } - - public function down(): void - { - \DB::table('system_settings')->whereIn('key', ['bible_verse_text', 'bible_verse_reference'])->delete(); - } -} diff --git a/database/migrations/2026_05_28_000001_add_cooperative_id_to_chapters.php b/database/migrations/2026_05_28_000001_add_cooperative_id_to_chapters.php deleted file mode 100644 index a680297..0000000 --- a/database/migrations/2026_05_28_000001_add_cooperative_id_to_chapters.php +++ /dev/null @@ -1,25 +0,0 @@ -unsignedBigInteger('cooperative_id')->nullable()->after('name'); - $table->foreign('cooperative_id')->references('id')->on('organizations')->nullOnDelete(); - $table->index('cooperative_id'); - }); - } - - public function down(): void - { - Schema::table('chapters', function (Blueprint $table) { - $table->dropForeign(['cooperative_id']); - $table->dropIndex(['cooperative_id']); - $table->dropColumn('cooperative_id'); - }); - } -}; diff --git a/database/migrations/2026_05_28_000002_add_role_to_chapter_members.php b/database/migrations/2026_05_28_000002_add_role_to_chapter_members.php deleted file mode 100644 index 27115df..0000000 --- a/database/migrations/2026_05_28_000002_add_role_to_chapter_members.php +++ /dev/null @@ -1,22 +0,0 @@ -string('role')->nullable()->after('position') - ->comment('Officer role: PRESIDENT, VICE_PRESIDENT, SECRETARY, TREASURER, AUDITOR, BOARD_MEMBER, MEMBER'); - }); - } - - public function down(): void - { - Schema::table('chapter_members', function (Blueprint $table) { - $table->dropColumn('role'); - }); - } -}; diff --git a/resources/js/Pages/AccountingDashboard.vue b/resources/js/Pages/AccountingDashboard.vue deleted file mode 100644 index d9792da..0000000 --- a/resources/js/Pages/AccountingDashboard.vue +++ /dev/null @@ -1,511 +0,0 @@ - - - - - diff --git a/resources/js/Pages/AddProductsToStore.vue b/resources/js/Pages/AddProductsToStore.vue deleted file mode 100644 index 29d3ac5..0000000 --- a/resources/js/Pages/AddProductsToStore.vue +++ /dev/null @@ -1,466 +0,0 @@ - - - - - diff --git a/resources/js/Pages/AddTransaction.vue b/resources/js/Pages/AddTransaction.vue deleted file mode 100644 index ed09a0d..0000000 --- a/resources/js/Pages/AddTransaction.vue +++ /dev/null @@ -1,401 +0,0 @@ - - - - - - - diff --git a/resources/js/Pages/AdminConsole.vue b/resources/js/Pages/AdminConsole.vue new file mode 100644 index 0000000..73cc038 --- /dev/null +++ b/resources/js/Pages/AdminConsole.vue @@ -0,0 +1,194 @@ + + + diff --git a/resources/js/Pages/AssignChapterOfficer.vue b/resources/js/Pages/AssignChapterOfficer.vue deleted file mode 100644 index 9b63f80..0000000 --- a/resources/js/Pages/AssignChapterOfficer.vue +++ /dev/null @@ -1,183 +0,0 @@ - - - - - diff --git a/resources/js/Pages/AssignProductToStore.vue b/resources/js/Pages/AssignProductToStore.vue deleted file mode 100644 index b09c3c9..0000000 --- a/resources/js/Pages/AssignProductToStore.vue +++ /dev/null @@ -1,922 +0,0 @@ - - - - - diff --git a/resources/js/Pages/Barangay/BlotterDetail.vue b/resources/js/Pages/Barangay/BlotterDetail.vue new file mode 100644 index 0000000..71420ea --- /dev/null +++ b/resources/js/Pages/Barangay/BlotterDetail.vue @@ -0,0 +1,152 @@ + + + diff --git a/resources/js/Pages/Barangay/BudgetLedger.vue b/resources/js/Pages/Barangay/BudgetLedger.vue new file mode 100644 index 0000000..6548a44 --- /dev/null +++ b/resources/js/Pages/Barangay/BudgetLedger.vue @@ -0,0 +1,178 @@ + + + diff --git a/resources/js/Pages/Barangay/DocumentRequestDetail.vue b/resources/js/Pages/Barangay/DocumentRequestDetail.vue new file mode 100644 index 0000000..8d02e02 --- /dev/null +++ b/resources/js/Pages/Barangay/DocumentRequestDetail.vue @@ -0,0 +1,188 @@ + + + diff --git a/resources/js/Pages/Barangay/ManageBlotters.vue b/resources/js/Pages/Barangay/ManageBlotters.vue new file mode 100644 index 0000000..abdae7e --- /dev/null +++ b/resources/js/Pages/Barangay/ManageBlotters.vue @@ -0,0 +1,180 @@ + + + diff --git a/resources/js/Pages/Barangay/ManageDocumentRequests.vue b/resources/js/Pages/Barangay/ManageDocumentRequests.vue new file mode 100644 index 0000000..0d0f42b --- /dev/null +++ b/resources/js/Pages/Barangay/ManageDocumentRequests.vue @@ -0,0 +1,113 @@ + + + diff --git a/resources/js/Pages/Barangay/ManageHouseholds.vue b/resources/js/Pages/Barangay/ManageHouseholds.vue new file mode 100644 index 0000000..3b04242 --- /dev/null +++ b/resources/js/Pages/Barangay/ManageHouseholds.vue @@ -0,0 +1,212 @@ + + + diff --git a/resources/js/Pages/Barangay/ManageProjects.vue b/resources/js/Pages/Barangay/ManageProjects.vue new file mode 100644 index 0000000..cfe6fe5 --- /dev/null +++ b/resources/js/Pages/Barangay/ManageProjects.vue @@ -0,0 +1,193 @@ + + + diff --git a/resources/js/Pages/Barangay/ManageRequestTypes.vue b/resources/js/Pages/Barangay/ManageRequestTypes.vue new file mode 100644 index 0000000..aa74573 --- /dev/null +++ b/resources/js/Pages/Barangay/ManageRequestTypes.vue @@ -0,0 +1,116 @@ + + + diff --git a/resources/js/Pages/Barangay/ManageResidents.vue b/resources/js/Pages/Barangay/ManageResidents.vue new file mode 100644 index 0000000..9ae49e9 --- /dev/null +++ b/resources/js/Pages/Barangay/ManageResidents.vue @@ -0,0 +1,213 @@ + + + diff --git a/resources/js/Pages/Barangay/RequestDocument.vue b/resources/js/Pages/Barangay/RequestDocument.vue new file mode 100644 index 0000000..ee8e861 --- /dev/null +++ b/resources/js/Pages/Barangay/RequestDocument.vue @@ -0,0 +1,137 @@ + + + diff --git a/resources/js/Pages/Barangay/ResidentProfile.vue b/resources/js/Pages/Barangay/ResidentProfile.vue new file mode 100644 index 0000000..a0b3f82 --- /dev/null +++ b/resources/js/Pages/Barangay/ResidentProfile.vue @@ -0,0 +1,238 @@ + + + diff --git a/resources/js/Pages/BatchAddCooperativeMembers.vue b/resources/js/Pages/BatchAddCooperativeMembers.vue deleted file mode 100644 index f88e585..0000000 --- a/resources/js/Pages/BatchAddCooperativeMembers.vue +++ /dev/null @@ -1,206 +0,0 @@ - - - - - diff --git a/resources/js/Pages/BatchAddCooperatives.vue b/resources/js/Pages/BatchAddCooperatives.vue deleted file mode 100644 index 956663a..0000000 --- a/resources/js/Pages/BatchAddCooperatives.vue +++ /dev/null @@ -1,201 +0,0 @@ - - - - - diff --git a/resources/js/Pages/BatchAddProducts.vue b/resources/js/Pages/BatchAddProducts.vue deleted file mode 100644 index 69ee78e..0000000 --- a/resources/js/Pages/BatchAddProducts.vue +++ /dev/null @@ -1,774 +0,0 @@ - - - - - diff --git a/resources/js/Pages/BatchAddStores.vue b/resources/js/Pages/BatchAddStores.vue deleted file mode 100644 index 2326d43..0000000 --- a/resources/js/Pages/BatchAddStores.vue +++ /dev/null @@ -1,183 +0,0 @@ - - - - - diff --git a/resources/js/Pages/BatchAddUsers.vue b/resources/js/Pages/BatchAddUsers.vue deleted file mode 100644 index 6c2b7ad..0000000 --- a/resources/js/Pages/BatchAddUsers.vue +++ /dev/null @@ -1,183 +0,0 @@ - - - - - diff --git a/resources/js/Pages/BuyViewProductMarket.vue b/resources/js/Pages/BuyViewProductMarket.vue deleted file mode 100644 index 774a0ca..0000000 --- a/resources/js/Pages/BuyViewProductMarket.vue +++ /dev/null @@ -1,323 +0,0 @@ - - - - - diff --git a/resources/js/Pages/CartProductMarket.vue b/resources/js/Pages/CartProductMarket.vue deleted file mode 100644 index f49253b..0000000 --- a/resources/js/Pages/CartProductMarket.vue +++ /dev/null @@ -1,366 +0,0 @@ - - - - - diff --git a/resources/js/Pages/CoopMemberSearch.vue b/resources/js/Pages/CoopMemberSearch.vue deleted file mode 100644 index 2b4fcc0..0000000 --- a/resources/js/Pages/CoopMemberSearch.vue +++ /dev/null @@ -1,112 +0,0 @@ - - - - - diff --git a/resources/js/Pages/CooperativeDetail.vue b/resources/js/Pages/CooperativeDetail.vue deleted file mode 100644 index 71ef15b..0000000 --- a/resources/js/Pages/CooperativeDetail.vue +++ /dev/null @@ -1,238 +0,0 @@ - - - - - diff --git a/resources/js/Pages/CooperativeHub.vue b/resources/js/Pages/CooperativeHub.vue deleted file mode 100644 index 775fdb2..0000000 --- a/resources/js/Pages/CooperativeHub.vue +++ /dev/null @@ -1,72 +0,0 @@ - - - - - diff --git a/resources/js/Pages/CooperativeList.vue b/resources/js/Pages/CooperativeList.vue deleted file mode 100644 index a1a8e8b..0000000 --- a/resources/js/Pages/CooperativeList.vue +++ /dev/null @@ -1,216 +0,0 @@ - - - - - diff --git a/resources/js/Pages/CooperativeMemberRegister.vue b/resources/js/Pages/CooperativeMemberRegister.vue deleted file mode 100644 index 1361ddd..0000000 --- a/resources/js/Pages/CooperativeMemberRegister.vue +++ /dev/null @@ -1,493 +0,0 @@ - - - - - diff --git a/resources/js/Pages/CreateCoopUser.vue b/resources/js/Pages/CreateCoopUser.vue deleted file mode 100644 index 35901e0..0000000 --- a/resources/js/Pages/CreateCoopUser.vue +++ /dev/null @@ -1,198 +0,0 @@ - - - - - diff --git a/resources/js/Pages/CreateCooperative.vue b/resources/js/Pages/CreateCooperative.vue deleted file mode 100644 index 79698f2..0000000 --- a/resources/js/Pages/CreateCooperative.vue +++ /dev/null @@ -1,364 +0,0 @@ - - - - - diff --git a/resources/js/Pages/CreateOrganization.vue b/resources/js/Pages/CreateOrganization.vue deleted file mode 100644 index 91cbbfd..0000000 --- a/resources/js/Pages/CreateOrganization.vue +++ /dev/null @@ -1,289 +0,0 @@ - - - - - diff --git a/resources/js/Pages/CreateProductStoreOwner.vue b/resources/js/Pages/CreateProductStoreOwner.vue deleted file mode 100644 index ce9ba80..0000000 --- a/resources/js/Pages/CreateProductStoreOwner.vue +++ /dev/null @@ -1,880 +0,0 @@ - - - - - diff --git a/resources/js/Pages/CreateProductUltimate.vue b/resources/js/Pages/CreateProductUltimate.vue deleted file mode 100644 index c02850f..0000000 --- a/resources/js/Pages/CreateProductUltimate.vue +++ /dev/null @@ -1,954 +0,0 @@ - - - - - - diff --git a/resources/js/Pages/CreateStore.vue b/resources/js/Pages/CreateStore.vue deleted file mode 100644 index 04cff8c..0000000 --- a/resources/js/Pages/CreateStore.vue +++ /dev/null @@ -1,733 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/resources/js/Pages/EditProductUltimate.vue b/resources/js/Pages/EditProductUltimate.vue deleted file mode 100644 index 8508376..0000000 --- a/resources/js/Pages/EditProductUltimate.vue +++ /dev/null @@ -1,617 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/js/Pages/EditStoreUltimate.vue b/resources/js/Pages/EditStoreUltimate.vue deleted file mode 100644 index aa1716e..0000000 --- a/resources/js/Pages/EditStoreUltimate.vue +++ /dev/null @@ -1,500 +0,0 @@ - - - - - diff --git a/resources/js/Pages/EnrollFarmer.vue b/resources/js/Pages/EnrollFarmer.vue deleted file mode 100644 index 91a6e00..0000000 --- a/resources/js/Pages/EnrollFarmer.vue +++ /dev/null @@ -1,304 +0,0 @@ - - - - - diff --git a/resources/js/Pages/FarmerProfileEdit.vue b/resources/js/Pages/FarmerProfileEdit.vue deleted file mode 100644 index 02019c3..0000000 --- a/resources/js/Pages/FarmerProfileEdit.vue +++ /dev/null @@ -1,122 +0,0 @@ - - - - - diff --git a/resources/js/Pages/Home.vue b/resources/js/Pages/Home.vue index f947b6a..12445eb 100644 --- a/resources/js/Pages/Home.vue +++ b/resources/js/Pages/Home.vue @@ -1,196 +1,104 @@ - - diff --git a/resources/js/Pages/ListProductsMarket.vue b/resources/js/Pages/ListProductsMarket.vue deleted file mode 100644 index 64fc325..0000000 --- a/resources/js/Pages/ListProductsMarket.vue +++ /dev/null @@ -1,139 +0,0 @@ - - - - - diff --git a/resources/js/Pages/ListProperties.vue b/resources/js/Pages/ListProperties.vue deleted file mode 100644 index 4dc68b7..0000000 --- a/resources/js/Pages/ListProperties.vue +++ /dev/null @@ -1,96 +0,0 @@ - - - - - diff --git a/resources/js/Pages/ListReferrals.vue b/resources/js/Pages/ListReferrals.vue deleted file mode 100644 index 28193bd..0000000 --- a/resources/js/Pages/ListReferrals.vue +++ /dev/null @@ -1,94 +0,0 @@ - - - - - diff --git a/resources/js/Pages/ListReports.vue b/resources/js/Pages/ListReports.vue deleted file mode 100644 index 9f029cb..0000000 --- a/resources/js/Pages/ListReports.vue +++ /dev/null @@ -1,107 +0,0 @@ - - - - - diff --git a/resources/js/Pages/ListStores.vue b/resources/js/Pages/ListStores.vue deleted file mode 100644 index ef5dc99..0000000 --- a/resources/js/Pages/ListStores.vue +++ /dev/null @@ -1,145 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/js/Pages/ManageAccounts.vue b/resources/js/Pages/ManageAccounts.vue deleted file mode 100644 index 02e85e6..0000000 --- a/resources/js/Pages/ManageAccounts.vue +++ /dev/null @@ -1,655 +0,0 @@ - - - - - diff --git a/resources/js/Pages/ManageGlobalTransactions.vue b/resources/js/Pages/ManageGlobalTransactions.vue deleted file mode 100644 index 659aea9..0000000 --- a/resources/js/Pages/ManageGlobalTransactions.vue +++ /dev/null @@ -1,358 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/js/Pages/ManageProductAdmin.vue b/resources/js/Pages/ManageProductAdmin.vue deleted file mode 100644 index 49cca6f..0000000 --- a/resources/js/Pages/ManageProductAdmin.vue +++ /dev/null @@ -1,314 +0,0 @@ - - - - - diff --git a/resources/js/Pages/ManageProductsAdmin.vue b/resources/js/Pages/ManageProductsAdmin.vue deleted file mode 100644 index 9e5c841..0000000 --- a/resources/js/Pages/ManageProductsAdmin.vue +++ /dev/null @@ -1,613 +0,0 @@ - - - - - diff --git a/resources/js/Pages/ManageStoresAdmin.vue b/resources/js/Pages/ManageStoresAdmin.vue deleted file mode 100644 index 0b06857..0000000 --- a/resources/js/Pages/ManageStoresAdmin.vue +++ /dev/null @@ -1,384 +0,0 @@ - - - - - diff --git a/resources/js/Pages/MyStores.vue b/resources/js/Pages/MyStores.vue deleted file mode 100644 index ada45f1..0000000 --- a/resources/js/Pages/MyStores.vue +++ /dev/null @@ -1,355 +0,0 @@ - - - - - diff --git a/resources/js/Pages/MyWallet.vue b/resources/js/Pages/MyWallet.vue deleted file mode 100644 index 81d1a7f..0000000 --- a/resources/js/Pages/MyWallet.vue +++ /dev/null @@ -1,364 +0,0 @@ - - - - - diff --git a/resources/js/Pages/PhotoViewer.vue b/resources/js/Pages/PhotoViewer.vue deleted file mode 100644 index aaa35a3..0000000 --- a/resources/js/Pages/PhotoViewer.vue +++ /dev/null @@ -1,206 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/js/Pages/PosAccessKeys.vue b/resources/js/Pages/PosAccessKeys.vue deleted file mode 100644 index a8510a9..0000000 --- a/resources/js/Pages/PosAccessKeys.vue +++ /dev/null @@ -1,372 +0,0 @@ - - - - - diff --git a/resources/js/Pages/PosHistory.vue b/resources/js/Pages/PosHistory.vue deleted file mode 100644 index 509e158..0000000 --- a/resources/js/Pages/PosHistory.vue +++ /dev/null @@ -1,161 +0,0 @@ - - - - - diff --git a/resources/js/Pages/PosMain.vue b/resources/js/Pages/PosMain.vue deleted file mode 100644 index 03cfb59..0000000 --- a/resources/js/Pages/PosMain.vue +++ /dev/null @@ -1,1087 +0,0 @@ - - - - - diff --git a/resources/js/Pages/RegisterCoop.vue b/resources/js/Pages/RegisterCoop.vue deleted file mode 100644 index 511c646..0000000 --- a/resources/js/Pages/RegisterCoop.vue +++ /dev/null @@ -1,493 +0,0 @@ - - - diff --git a/resources/js/Pages/RemoveProductFromStoreAdmin.vue b/resources/js/Pages/RemoveProductFromStoreAdmin.vue deleted file mode 100644 index 9fdffbd..0000000 --- a/resources/js/Pages/RemoveProductFromStoreAdmin.vue +++ /dev/null @@ -1,271 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/js/Pages/ShipmentDetail.vue b/resources/js/Pages/ShipmentDetail.vue deleted file mode 100644 index 55a8d4f..0000000 --- a/resources/js/Pages/ShipmentDetail.vue +++ /dev/null @@ -1,141 +0,0 @@ - - - - - diff --git a/resources/js/Pages/ShipmentList.vue b/resources/js/Pages/ShipmentList.vue deleted file mode 100644 index 9909f5f..0000000 --- a/resources/js/Pages/ShipmentList.vue +++ /dev/null @@ -1,100 +0,0 @@ - - - - - diff --git a/resources/js/Pages/TransferCredit.vue b/resources/js/Pages/TransferCredit.vue deleted file mode 100644 index e916c03..0000000 --- a/resources/js/Pages/TransferCredit.vue +++ /dev/null @@ -1,143 +0,0 @@ - - - - - diff --git a/resources/js/Pages/TransferMyCredit.vue b/resources/js/Pages/TransferMyCredit.vue deleted file mode 100644 index 33a1ade..0000000 --- a/resources/js/Pages/TransferMyCredit.vue +++ /dev/null @@ -1,219 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/js/Pages/UltimateConsole.vue b/resources/js/Pages/UltimateConsole.vue deleted file mode 100644 index dc8a3a7..0000000 --- a/resources/js/Pages/UltimateConsole.vue +++ /dev/null @@ -1,1627 +0,0 @@ - - - - - - - diff --git a/resources/js/Pages/VerificationDashboard.vue b/resources/js/Pages/VerificationDashboard.vue deleted file mode 100644 index 7d9e05e..0000000 --- a/resources/js/Pages/VerificationDashboard.vue +++ /dev/null @@ -1,108 +0,0 @@ - - - - - diff --git a/resources/js/Pages/ViewAllPhotos.vue b/resources/js/Pages/ViewAllPhotos.vue deleted file mode 100644 index 197f114..0000000 --- a/resources/js/Pages/ViewAllPhotos.vue +++ /dev/null @@ -1,250 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/js/Pages/ViewStoreMarket.vue b/resources/js/Pages/ViewStoreMarket.vue deleted file mode 100644 index 0f6ee2f..0000000 --- a/resources/js/Pages/ViewStoreMarket.vue +++ /dev/null @@ -1,580 +0,0 @@ - - - - - diff --git a/resources/js/composables/Core/useAuth.js b/resources/js/composables/Core/useAuth.js index 24af6d0..c388c8c 100644 --- a/resources/js/composables/Core/useAuth.js +++ b/resources/js/composables/Core/useAuth.js @@ -1,53 +1,35 @@ -import { ref, computed, onMounted } from 'vue'; +import { ref, computed } from 'vue'; import axios from 'axios'; -import { UserTypes } from '../../utils/UserTypes.js'; +import { UserTypes, ADMIN_ROLES, STAFF_ROLES } from '../../utils/UserTypes.js'; import { useUserStore } from '../../stores/user.js'; -// Global reactive state to persist throughout the SPA session const globalRole = ref(sessionStorage.getItem('user_acct_type') || UserTypes.PUBLIC); const isFetching = ref(false); -/** - * Fetches the user account type from the server. - * Ensures only one request is made per session. - */ async function fetchRole() { if (isFetching.value) return; - - // If we already have a specialized role in sessionStorage, don't fetch again const cached = sessionStorage.getItem('user_acct_type'); - if (cached && cached !== UserTypes.PUBLIC) { - return; - } + if (cached && cached !== UserTypes.PUBLIC) return; isFetching.value = true; try { const response = await axios.get('/get/user/acct-type'); let acctType = response.data?.acct_type; - - // Handle case where acct_type might be an object (Enum serialization) - if (acctType && typeof acctType === 'object' && acctType.value) { - acctType = acctType.value; - } - + if (acctType && typeof acctType === 'object' && acctType.value) acctType = acctType.value; if (acctType) { globalRole.value = acctType; sessionStorage.setItem('user_acct_type', acctType); } } catch (error) { - // If 401, we are likely a guest if (error.response?.status === 401) { globalRole.value = UserTypes.PUBLIC; sessionStorage.setItem('user_acct_type', UserTypes.PUBLIC); - } else { - console.warn('Failed to fetch user acct type from server:', error); } } finally { isFetching.value = false; } } -// Initial fetch attempt if we don't have a definitive role if (!sessionStorage.getItem('user_acct_type') || sessionStorage.getItem('user_acct_type') === UserTypes.PUBLIC) { fetchRole(); } @@ -58,90 +40,79 @@ export function resetRole() { sessionStorage.removeItem('user_acct_type'); } -/** - * Composable for managing user roles and permissions in the frontend. - * @param {Object} [user] - Optional user object for legacy support or specific overrides - */ export function useAuth(user = null) { const userStore = useUserStore(); - - // Priority: Explicitly passed user prop > user store > sessionStorage fallback + const currentUser = computed(() => { if (user?.value ?? user) return user?.value ?? user; const storeUser = userStore.user; if (storeUser && Object.keys(storeUser).length > 0) return storeUser; - try { const stored = sessionStorage.getItem('currentUser'); return stored ? JSON.parse(stored) : null; - } catch (e) { + } catch { return null; } }); const role = computed(() => { - // Priority 1: User object passed directly or from store const localRole = userStore.acctType || currentUser.value?.acct_type; if (localRole) { if (typeof localRole === 'object' && localRole.value) return localRole.value; return localRole; } - - // Priority 2: Global fetched role return globalRole.value; }); const hasRole = (targetRole) => { - if (Array.isArray(targetRole)) { - return targetRole.some(r => role.value === r); - } + if (Array.isArray(targetRole)) return targetRole.some(r => role.value === r); return role.value === targetRole; }; - // Role-specific helpers - const isUltimate = computed(() => role.value === UserTypes.ULTIMATE); - const isSuperOperator = computed(() => role.value === UserTypes.SUPER_OPERATOR); - const isOperator = computed(() => role.value === UserTypes.OPERATOR); - const isCoordinator = computed(() => role.value === UserTypes.COORDINATOR); - const isStoreOwner = computed(() => role.value === UserTypes.STORE_OWNER); - const isStoreManager = computed(() => role.value === UserTypes.STORE_MANAGER); - const isRider = computed(() => role.value === UserTypes.RIDER); - const isSupplier = computed(() => role.value === UserTypes.SUPPLIER); - const isSupplierOverseer = computed(() => role.value === UserTypes.SUPPLIER_OVERSEER); - const isWholesaleBuyer = computed(() => role.value === UserTypes.WHOLESALE_BUYER); - const isAudit = computed(() => role.value === UserTypes.AUDIT); - const isUser = computed(() => role.value === UserTypes.USER); - const isCoopOfficer = computed(() => role.value === UserTypes.COOP_OFFICER); - const isCoopMember = computed(() => role.value === UserTypes.COOP_MEMBER); - const isPOSTerminal = computed(() => role.value === UserTypes.POS_TERMINAL); - const isPublic = computed(() => role.value === UserTypes.PUBLIC || !userStore.isLoggedIn); - const isLoggedIn = computed(() => userStore.isLoggedIn); + // Barangay-specific role helpers + const isSuperAdmin = computed(() => role.value === UserTypes.SUPER_ADMIN); + const isPunongBarangay = computed(() => role.value === UserTypes.PUNONG_BARANGAY); + const isKagawad = computed(() => role.value === UserTypes.KAGAWAD); + const isSecretary = computed(() => role.value === UserTypes.SECRETARY); + const isTreasurer = computed(() => role.value === UserTypes.TREASURER); + const isSkChairperson = computed(() => role.value === UserTypes.SK_CHAIRPERSON); + const isSkCouncilor = computed(() => role.value === UserTypes.SK_COUNCILOR); + const isTanod = computed(() => role.value === UserTypes.TANOD); + const isBhw = computed(() => role.value === UserTypes.BHW); + const isDaycareWorker = computed(() => role.value === UserTypes.DAYCARE_WORKER); + const isStaff = computed(() => role.value === UserTypes.STAFF); + const isResident = computed(() => role.value === UserTypes.RESIDENT); + const isAudit = computed(() => role.value === UserTypes.AUDIT); + const isPublic = computed(() => role.value === UserTypes.PUBLIC || !userStore.isLoggedIn); + const isLoggedIn = computed(() => userStore.isLoggedIn); + + // Group helpers + const isAdmin = computed(() => ADMIN_ROLES.includes(role.value)); + const isBarangayStaff = computed(() => STAFF_ROLES.includes(role.value)); return { user: currentUser, role, hasRole, - isUltimate, - isSuperOperator, - isOperator, - isCoordinator, - isStoreOwner, - isStoreManager, - isRider, - isSupplier, - isSupplierOverseer, - isWholesaleBuyer, + isSuperAdmin, + isPunongBarangay, + isKagawad, + isSecretary, + isTreasurer, + isSkChairperson, + isSkCouncilor, + isTanod, + isBhw, + isDaycareWorker, + isStaff, + isResident, isAudit, - isUser, - isCoopOfficer, - isCoopMember, - isPOSTerminal, isPublic, isLoggedIn, + isAdmin, + isBarangayStaff, UserTypes, refreshRole: fetchRole, - // Expose the user store for direct access to user data - userStore + userStore, }; } - diff --git a/resources/js/composables/Market/usePosSession.js b/resources/js/composables/Market/usePosSession.js deleted file mode 100644 index b75e1e2..0000000 --- a/resources/js/composables/Market/usePosSession.js +++ /dev/null @@ -1,162 +0,0 @@ -import { ref, onMounted, watch } from 'vue'; -import { usePosStore } from '../../stores/pos'; -import { useUIStore } from '../../stores/ui'; -import { useOfflineStore } from '../useOfflineStore'; -import { useNetworkStore } from '../../stores/network'; - -export function usePosSession(props) { - const posStore = usePosStore(); - const uiStore = useUIStore(); - const offlineStore = useOfflineStore(); - const networkStore = useNetworkStore(); - const storeHash = ref(null); - const showSuccessAnimation = ref(false); - const isOfflineMode = ref(false); - - // Helper: Access key from URL or LocalStorage - const getStoredAccessKey = () => { - try { - return localStorage.getItem('pos_access_key'); - } catch { - return null; - } - }; - - const initialize = async () => { - // Check for existing session in URL or Storage - const urlParams = new URLSearchParams(window.location.search); - const accessKey = props.access_key || urlParams.get('key') || getStoredAccessKey(); - const hashkey = props.target; - - if (accessKey) { - localStorage.setItem('pos_access_key', accessKey); - } - - // 1. Load Session or treat as Store Hash - if (hashkey) { - // Try loading as session - await posStore.loadSession(hashkey, accessKey); - - if (!posStore.activeSession) { - // Not a session? Treat it as a direct link to a store terminal - storeHash.value = hashkey; - posStore.error = null; - } else if (posStore.activeSession?.store?.hashkey) { - storeHash.value = posStore.activeSession.store.hashkey; - } - } - - // 2. Fetch products (Always synchronized with access key/store hash) - await posStore.fetchProducts(accessKey, storeHash.value); - - // 3. Fallback: If no direct session yet, try to load one by access key - if (!posStore.activeSession && accessKey) { - await posStore.loadSession(null, accessKey); - } - - // 4. Synchronization: ensure products are loaded if session was found later - if (posStore.activeSession && posStore.products.length === 0) { - await posStore.fetchProducts(accessKey, storeHash.value); - } - - // 5. Restore offline cart if no server session was found - if (!posStore.activeSession) { - try { - const savedRaw = localStorage.getItem('pos_cart_session') - const saved = savedRaw ? JSON.parse(savedRaw) : null - if (saved?.offline && saved.cart?.length > 0) { - posStore.activeSession = { hashkey: saved.sessionHashkey, offline: true, transactions: [] } - posStore.cart = saved.cart - posStore.isOfflineMode = true - isOfflineMode.value = true - } - } catch {} - } - - // 6. Load terminal stats - await posStore.fetchTodayStats(storeHash.value); - }; - - const completeTransaction = async (customerName) => { - const currentStoreHash = storeHash.value || posStore.activeSession?.store?.hashkey; - - // Try Online First (skip if session is a local offline-only session) - let success = false; - const isOfflineSession = posStore.activeSession?.offline === true; - if (networkStore.isOnline && !isOfflineSession) { - success = await posStore.completeTransaction(customerName); - } - - if (!success) { - // Offline Fallback - const txnData = { - store_hash: currentStoreHash, - customer_name: customerName, - items: posStore.cart.map(item => ({ - product_hashkey: item.product?.hashkey, - quantity: item.quantity, - price_at_sale: item.price_at_sale - })), - total: posStore.totalAmount, - received: posStore.receivedAmount, - change: posStore.changeAmount, - method: posStore.paymentMethod - }; - - const id = await offlineStore.storeTransactionOffline(txnData); - if (id) { - success = true; - isOfflineMode.value = true; - } - } - - if (success) { - showSuccessAnimation.value = true; - - // Clean up state - posStore.resetSession(); - - // Delayed re-initialization (gives time for animation) - setTimeout(async () => { - showSuccessAnimation.value = false; - if (networkStore.isOnline) { - await posStore.startNewSession(currentStoreHash, '', getStoredAccessKey()); - await Promise.all([ - posStore.fetchTodayStats(currentStoreHash), - posStore.fetchPosSessions(currentStoreHash, 1) - ]); - isOfflineMode.value = false; - } - }, 2500); - - return true; - } - return false; - }; - - const startNewSessionSilently = async () => { - const accessKey = getStoredAccessKey(); - if (!storeHash.value && !accessKey) { - posStore.error = 'No store selected. Open the POS from a store page or use an access key.'; - return false; - } - return await posStore.startNewSession(storeHash.value, '', accessKey); - }; - - // Keep storeHash in sync with active session if it changes - watch(() => posStore.activeSession, (session) => { - if (session?.store?.hashkey) { - storeHash.value = session.store.hashkey; - } - }, { immediate: true }); - - return { - storeHash, - showSuccessAnimation, - initialize, - completeTransaction, - startNewSessionSilently, - getStoredAccessKey, - isOfflineMode - }; -} diff --git a/resources/js/composables/useGlobalTransactions.js b/resources/js/composables/useGlobalTransactions.js deleted file mode 100644 index 532c1ca..0000000 --- a/resources/js/composables/useGlobalTransactions.js +++ /dev/null @@ -1,29 +0,0 @@ -import { computed, onMounted } from 'vue'; -import { useGlobalTransactionStore } from '../stores/globalTransaction'; - -export function useGlobalTransactions(filters = null) { - const store = useGlobalTransactionStore(); - - const transactions = computed(() => store.transactions); - const isLoading = computed(() => store.isLoading); - const error = computed(() => store.error); - - const fetchTransactions = async (newFilters = null) => { - return await store.fetchTransactions(newFilters || filters || {}); - }; - - const getProductTransactions = (productHash) => { - return computed(() => store.getTransactionsByProduct(productHash)); - }; - - const precache = () => store.precache(); - - return { - transactions, - isLoading, - error, - fetchTransactions, - getProductTransactions, - precache - }; -} diff --git a/resources/js/composables/usePhotoList.js b/resources/js/composables/usePhotoList.js deleted file mode 100644 index 2ae400c..0000000 --- a/resources/js/composables/usePhotoList.js +++ /dev/null @@ -1,56 +0,0 @@ -import { ref } from 'vue'; -import axios from 'axios'; -import { useFileBlobCache } from './useFileBlobCache'; - -/** - * Composable for fetching and managing a list of photos for a specific entity. - */ -export function usePhotoList() { - const { getFile, preCacheFiles, blobCache } = useFileBlobCache(); - - const photos = ref([]); - const loading = ref(false); - const error = ref(null); - - /** - * Fetch photos for a target hash and type. - * - * @param {string} targetHash - * @param {string} type - 'StoreMarket', 'ProductMarket', 'User' - */ - const fetchPhotos = async (targetHash, type = 'StoreMarket') => { - if (!targetHash) return; - - loading.value = true; - error.value = null; - - try { - const response = await axios.post(`/Request/Photos/${type}`, { - target: targetHash - }); - - if (response.data && Array.isArray(response.data)) { - photos.value = response.data; - // Pre-cache all blobs for these hashes - await preCacheFiles(photos.value); - } else { - photos.value = []; - } - } catch (err) { - console.error('Error fetching photos:', err); - error.value = 'Failed to load photos.'; - photos.value = []; - } finally { - loading.value = false; - } - }; - - return { - photos, - loading, - error, - fetchPhotos, - blobCache, - getFile - }; -} diff --git a/resources/js/composables/useUltimate.js b/resources/js/composables/useUltimate.js deleted file mode 100644 index c4fe491..0000000 --- a/resources/js/composables/useUltimate.js +++ /dev/null @@ -1,204 +0,0 @@ -import { ref } from 'vue'; -import axios from 'axios'; - -export function useUltimate() { - const loading = ref(false); - const stats = ref(null); - const queryResults = ref(null); - const affectedRows = ref(0); - const commandOutput = ref(''); - - const getStats = async () => { - loading.value = true; - try { - const response = await axios.post('/admin/ultimate/stats'); - if (response.data.success) { - stats.value = response.data.data; - } - return response.data; - } catch (error) { - console.error('Failed to fetch stats:', error); - throw error; - } finally { - loading.value = false; - } - }; - - const runQuery = async (query) => { - loading.value = true; - try { - const response = await axios.post('/admin/ultimate/query', { query }); - if (response.data.success) { - queryResults.value = response.data.data || null; - affectedRows.value = response.data.affected || 0; - } - return response.data; - } catch (error) { - console.error('Failed to run query:', error); - throw error; - } finally { - loading.value = false; - } - }; - - const toggleMaintenance = async (enabled) => { - loading.value = true; - try { - const response = await axios.post('/admin/ultimate/maintenance/toggle', { enabled }); - if (response.data.success && stats.value) { - stats.value.maintenance_mode = response.data.maintenance_mode; - } - return response.data; - } catch (error) { - console.error('Failed to toggle maintenance:', error); - throw error; - } finally { - loading.value = false; - } - }; - - const sendGlobalMessage = async (message, type = 'info') => { - loading.value = true; - try { - return await axios.post('/admin/ultimate/global-message', { message, type }); - } catch (error) { - console.error('Failed to send global message:', error); - throw error; - } finally { - loading.value = false; - } - }; - - const flushData = async (target) => { - loading.value = true; - try { - return await axios.post('/admin/ultimate/flush', { target }); - } catch (error) { - console.error('Failed to flush data:', error); - throw error; - } finally { - loading.value = false; - } - }; - - const testNotification = async (userHash) => { - loading.value = true; - try { - return await axios.post('/admin/ultimate/test-notification', { user_hash: userHash }); - } catch (error) { - console.error('Failed to test notification:', error); - throw error; - } finally { - loading.value = false; - } - }; - - const batchManage = async (action, ids, data = {}) => { - loading.value = true; - try { - return await axios.post('/admin/ultimate/batch', { action, ids, data }); - } catch (error) { - console.error('Failed to run batch operation:', error); - throw error; - } finally { - loading.value = false; - } - }; - - const runCommand = async (command) => { - loading.value = true; - try { - const response = await axios.post('/admin/ultimate/command', { command }); - if (response.data.success) { - commandOutput.value = response.data.output; - } - return response.data; - } catch (error) { - console.error('Failed to run command:', error); - throw error; - } finally { - loading.value = false; - } - }; - - const getLogs = async (type = 'database') => { - loading.value = true; - try { - const response = await axios.post('/admin/ultimate/logs', { type }); - return response.data; - } catch (error) { - console.error('Failed to fetch logs:', error); - throw error; - } finally { - loading.value = false; - } - }; - - const downloadBackup = () => { - // Simple window location change to trigger GET download - window.location.href = '/admin/ultimate/backup/download'; - }; - - const getBackups = async () => { - loading.value = true; - try { - const response = await axios.post('/admin/ultimate/backups/list'); - return response.data; - } catch (error) { - console.error('Failed to fetch backups:', error); - throw error; - } finally { - loading.value = false; - } - }; - - const downloadBackupByHash = (hash) => { - window.location.href = `/admin/ultimate/backup/download/hash?hash=${hash}`; - }; - - const renameBackup = async (hash, name) => { - loading.value = true; - try { - return await axios.post('/admin/ultimate/backup/rename', { hash, name }); - } catch (error) { - console.error('Failed to rename backup:', error); - throw error; - } finally { - loading.value = false; - } - }; - - const deleteBackup = async (hash) => { - loading.value = true; - try { - return await axios.post('/admin/ultimate/backup/delete', { hash }); - } catch (error) { - console.error('Failed to delete backup:', error); - throw error; - } finally { - loading.value = false; - } - }; - - return { - loading, - stats, - queryResults, - affectedRows, - commandOutput, - getStats, - runQuery, - toggleMaintenance, - sendGlobalMessage, - flushData, - testNotification, - batchManage, - runCommand, - getLogs, - downloadBackup, - getBackups, - downloadBackupByHash, - renameBackup, - deleteBackup - }; -} diff --git a/resources/js/composables/useUserAdditionalDetails.js b/resources/js/composables/useUserAdditionalDetails.js deleted file mode 100644 index 7812980..0000000 --- a/resources/js/composables/useUserAdditionalDetails.js +++ /dev/null @@ -1,131 +0,0 @@ -import { ref, onMounted } from 'vue'; -import axios from 'axios'; - -export function useUserAdditionalDetails() { - const details = ref({ - settings: {}, - details: {} - }); - const isLoading = ref(false); - const joinedCooperatives = ref([]); - - const fetchUserDetails = async () => { - isLoading.value = true; - try { - const response = await axios.post('/UserAdditionalDetails/Get'); - if (response.data.success) { - details.value = response.data.data; - } - } catch (error) { - console.error('Failed to fetch user additional details:', error); - } finally { - isLoading.value = false; - } - }; - - const joinCooperative = async (cooperativeHash) => { - isLoading.value = true; - try { - const response = await axios.post('/UserAdditionalDetails/UpdateCooperatives', { - cooperative_hash: cooperativeHash, - action: 'add' - }); - if (response.data.success) { - // If it was successful, update the local settings to reflect it - if (!details.value.settings.cooperatives) { - details.value.settings.cooperatives = []; - } - if (!details.value.settings.cooperatives.includes(cooperativeHash)) { - details.value.settings.cooperatives.push(cooperativeHash); - } - return true; - } - } catch (error) { - console.error('Failed to join cooperative:', error); - } finally { - isLoading.value = false; - } - return false; - }; - - const leaveCooperative = async (cooperativeHash) => { - isLoading.value = true; - try { - const response = await axios.post('/UserAdditionalDetails/UpdateCooperatives', { - cooperative_hash: cooperativeHash, - action: 'remove' - }); - if (response.data.success) { - if (details.value.settings.cooperatives) { - details.value.settings.cooperatives = details.value.settings.cooperatives.filter(h => h !== cooperativeHash); - } - return true; - } - } catch (error) { - console.error('Failed to leave cooperative:', error); - } finally { - isLoading.value = false; - } - return false; - }; - - const fetchJoinedCooperatives = async (userHash = null) => { - isLoading.value = true; - try { - const response = await axios.post('/UserAdditionalDetails/GetCooperatives', { - user_hash: userHash - }); - if (response.data.success) { - joinedCooperatives.value = response.data.data; - return joinedCooperatives.value; - } - } catch (error) { - console.error('Failed to fetch user cooperatives:', error); - } finally { - isLoading.value = false; - } - return []; - }; - - const searchUsersByCooperative = async (cooperativeHash) => { - isLoading.value = true; - try { - const response = await axios.post('/UserAdditionalDetails/SearchByCooperative', { - cooperative_hash: cooperativeHash - }); - if (response.data.success) { - return response.data.data; - } - } catch (error) { - console.error('Failed to search users by cooperative:', error); - } finally { - isLoading.value = false; - } - return []; - }; - - const getJoinedCooperativeHashes = () => { - return details.value.settings.cooperatives || []; - }; - - const hasJoinedCooperative = (cooperativeHash) => { - return getJoinedCooperativeHashes().includes(cooperativeHash); - }; - - onMounted(() => { - fetchUserDetails(); - }); - - return { - details, - isLoading, - joinedCooperatives, - fetchUserDetails, - joinCooperative, - leaveCooperative, - fetchJoinedCooperatives, - searchUsersByCooperative, - getJoinedCooperativeHashes, - hasJoinedCooperative - }; -} diff --git a/resources/js/stores/globalTransaction.js b/resources/js/stores/globalTransaction.js deleted file mode 100644 index 7240cda..0000000 --- a/resources/js/stores/globalTransaction.js +++ /dev/null @@ -1,58 +0,0 @@ -import { defineStore } from 'pinia'; -import axios from 'axios'; - -export const useGlobalTransactionStore = defineStore('globalTransaction', { - state: () => ({ - transactions: [], - isLoading: false, - error: null, - lastFetched: null, - }), - - getters: { - getTransactionsByProduct: (state) => (productHash) => { - return state.transactions.filter(tx => tx.product_hash === productHash); - }, - getTransactionsByStore: (state) => (storeHash) => { - return state.transactions.filter(tx => tx.store_hash === storeHash); - } - }, - - actions: { - async fetchTransactions(filters = {}) { - try { - this.isLoading = true; - this.error = null; - - const response = await axios.post('/admin/transactions/list', filters); - - if (Array.isArray(response.data)) { - this.transactions = response.data; - this.lastFetched = Date.now(); - } - - return this.transactions; - } catch (err) { - this.error = 'Failed to load transactions'; - console.error('Error fetching transactions:', err); - throw err; - } finally { - this.isLoading = false; - } - }, - - async precache() { - // Avoid refetching if data is fresh (less than 5 minutes old) - if (this.transactions.length > 0 && this.lastFetched && (Date.now() - this.lastFetched < 300000)) { - return; - } - - await this.fetchTransactions(); - }, - - clearCache() { - this.transactions = []; - this.lastFetched = null; - } - } -}); diff --git a/resources/js/stores/pos.js b/resources/js/stores/pos.js deleted file mode 100644 index 847340b..0000000 --- a/resources/js/stores/pos.js +++ /dev/null @@ -1,439 +0,0 @@ -import { defineStore } from 'pinia' -import axios from 'axios' -import { db } from '../db' -import { useNetworkStore } from './network' - -export const usePosStore = defineStore('pos', { - state: () => ({ - activeSession: null, - cart: [], - products: [], - categories: [], - loading: false, - error: null, - receivedAmount: 0, - paymentMethod: 'cash', - paymentField: '', - todayStats: { count: 0, total: 0, store_name: null, store_photo: null }, - customerSuggestions: [], - cachedCustomers: JSON.parse(localStorage.getItem('pos_cached_customers') || '[]'), - posSessions: [], - posSessionsCount: 0, - posSessionsPage: 1, - isOfflineMode: false, - lastSync: localStorage.getItem('pos_last_sync') || null, - }), - - getters: { - totalAmount: (state) => { - return state.cart.reduce((sum, item) => sum + (item.total_price || 0), 0) - }, - changeAmount: (state) => { - return Math.max(0, state.receivedAmount - state.totalAmount) - }, - itemsCount: (state) => { - return state.cart.reduce((count, item) => count + (item.quantity || 0), 0) - } - }, - - actions: { - async fetchProducts(accessKey = null, storeHash = null) { - this.loading = true - try { - // Try Local DB first if offline - if (this.isOfflineMode) { - const localProducts = await db.products.toArray() - if (localProducts.length > 0) { - this.products = localProducts - this.loading = false - return - } - } - - const params = {} - if (accessKey) params.access_key = accessKey - if (storeHash) params.store_hash = storeHash - if (this.activeSession) params.session_hash = this.activeSession.hashkey - - const response = await axios.post('/Market/Products/List', params) - this.products = response.data || [] - - // Save to local DB for next time - if (this.products.length > 0) { - await db.products.clear() - await db.products.bulkPut(this.products) - this.lastSync = new Date().toISOString() - localStorage.setItem('pos_last_sync', this.lastSync) - } - - // Extract unique categories - const cats = new Set(this.products.map(p => p.category).filter(Boolean)) - this.categories = Array.from(cats) - } catch (error) { - console.error('Failed to fetch products:', error) - // Fallback to local DB on network error - const localProducts = await db.products.toArray() - if (localProducts.length > 0) { - this.products = localProducts - } else if (this.products.length === 0) { - // Only surface the error if nothing is already showing - this.error = 'Failed to load products' - } - } finally { - this.loading = false - } - }, - - async startNewSession(storeHash, customerName = '', accessKey = null) { - if (this.loading) return - this.loading = true - try { - const networkStore = useNetworkStore() - - const payload = { - customer_name: customerName - } - - if (!networkStore.isOnline) { - const savedRaw = localStorage.getItem('pos_cart_session') - const saved = savedRaw ? JSON.parse(savedRaw) : null - if (saved?.offline && saved.cart?.length > 0) { - this.activeSession = { hashkey: saved.sessionHashkey, offline: true, transactions: [] } - this.cart = saved.cart - } else { - this.activeSession = { - hashkey: 'offline-' + Date.now(), - customer_name: customerName, - offline: true, - transactions: [] - } - this.cart = [] - } - return - } - - if (accessKey) payload.access_key = accessKey - if (storeHash) payload.store_hash = storeHash - - const response = await axios.post('/api/pos/start', payload) - if (response.data && response.data.success) { - await this.loadSession(response.data.data.hashkey) - } - } catch (error) { - console.error('Failed to start session:', error) - const serverMessage = error?.response?.data?.message - this.error = serverMessage || 'Failed to start session' - } finally { - this.loading = false - } - }, - - async loadSession(hashkey = null, accessKey = null) { - this.loading = true - try { - const params = {} - if (hashkey) params.target = hashkey - if (accessKey) params.access_key = accessKey - - const response = await axios.post('/api/pos/session', params) - if (response.data && response.data.success) { - this.activeSession = response.data.data - this.cart = this.activeSession.transactions || [] - if (this.cart.length === 0) { - this._restoreCartFromStorage(this.activeSession.hashkey) - } - } else { - this.error = 'Session not found or invalid' - } - } catch (error) { - console.error('Failed to load session:', error) - this.error = 'Failed to load session' - } finally { - this.loading = false - } - }, - - async addToCart(productHash, quantity = 1, customPrice = null) { - if (!this.activeSession && !this.isOfflineMode) return - - // Offline Fallback - if (this.isOfflineMode || !navigator.onLine) { - const product = this.products.find(p => p.hashkey === productHash); - if (!product) return; - - const price = customPrice !== null ? customPrice : (product.price || 0); - const existingIndex = this.cart.findIndex(item => item.product?.hashkey === productHash); - - if (existingIndex !== -1) { - if (quantity <= 0) { - this.cart.splice(existingIndex, 1); - } else { - const item = this.cart[existingIndex]; - item.quantity = quantity; - item.price_at_sale = price; - item.total_price = item.quantity * item.price_at_sale; - } - } else if (quantity > 0) { - this.cart.push({ - id: 'local-' + Date.now(), - product: product, - quantity: quantity, - price_at_sale: price, - total_price: price * quantity - }); - } - this._saveCartToStorage() - return; - } - - try { - const payload = { - session_hash: this.activeSession.hashkey, - product_hash: productHash, - quantity: quantity - } - if (customPrice !== null) payload.price = customPrice - - const response = await axios.post('/api/pos/add-item', payload) - if (response.data && response.data.success) { - // Update session state directly from the response - this.activeSession = response.data.data - this.cart = this.activeSession.transactions || [] - this._saveCartToStorage() - } - } catch (error) { - console.error('Failed to add item:', error) - this.error = 'Failed to add item to cart' - } - }, - - async removeFromCart(transactionId) { - if (!this.activeSession) return - - if (this.isOfflineMode || !navigator.onLine) { - this.cart = this.cart.filter(item => item.id !== transactionId); - return; - } - - try { - const response = await axios.post('/api/pos/remove-item', { - session_hash: this.activeSession.hashkey, - transaction_id: transactionId - }) - if (response.data && response.data.success) { - // Update session state directly from the response - this.activeSession = response.data.data - this.cart = this.activeSession.transactions || [] - } - } catch (error) { - console.error('Failed to remove item:', error) - } - }, - - async completeTransaction(customerName = '') { - if (!this.activeSession || this.loading) return - - this.loading = true - try { - const response = await axios.post('/api/pos/complete', { - session_hash: this.activeSession.hashkey, - received_amount: this.receivedAmount, - payment_method: this.paymentMethod, - payment_field: this.paymentField, - customer_name: customerName - }) - if (response.data && response.data.success) { - this.activeSession = response.data.data - this._clearCartStorage() - - // Add customer to cache if provided - if (customerName) { - this.updateCustomerCache([{ - name: customerName, - hashkey: 'new-' + Date.now() // Temporary hash until next fetch - }]); - } - - return true - } - } catch (error) { - console.error('Failed to complete transaction:', error) - this.error = 'Payment failed' - } finally { - this.loading = false - } - return false - }, - - _saveCartToStorage() { - if (!this.activeSession) return - try { - localStorage.setItem('pos_cart_session', JSON.stringify({ - sessionHashkey: this.activeSession.hashkey, - offline: this.activeSession.offline || false, - cart: this.cart - })) - } catch {} - }, - - _restoreCartFromStorage(sessionHashkey) { - try { - const raw = localStorage.getItem('pos_cart_session') - if (!raw) return false - const saved = JSON.parse(raw) - if (saved.sessionHashkey === sessionHashkey && saved.cart?.length > 0) { - this.cart = saved.cart - return true - } - } catch {} - return false - }, - - _clearCartStorage() { - localStorage.removeItem('pos_cart_session') - }, - - resetSession() { - this.activeSession = null - this.cart = [] - this.receivedAmount = 0 - this.paymentField = '' - this._clearCartStorage() - // Keep the access key as it defines the terminal session - }, - - async fetchTodayStats(storeHash = null) { - try { - const response = await axios.post('/api/pos/stats', { - store_hash: storeHash - }) - if (response.data && response.data.success) { - this.todayStats = response.data.data - } - } catch (error) { - console.error('Failed to fetch today stats:', error) - } - }, - - async fetchCustomerSuggestions(query = '', storeHash = null) { - // 1. Show cached results immediately if available - if (this.cachedCustomers.length > 0) { - const q = query.toLowerCase(); - this.customerSuggestions = query - ? this.cachedCustomers.filter(c => c.name.toLowerCase().includes(q)).slice(0, 10) - : this.cachedCustomers.slice(-3).reverse(); // Show last 3 most recent if no query - } - - // 2. Background fetch to get current/complete list - try { - const response = await axios.post('/api/pos/get-customers', { - query: query, - store_hash: storeHash - }) - if (response.data && response.data.success) { - const newSuggestions = response.data.data; - this.customerSuggestions = newSuggestions; - - // Update persistent cache - this.updateCustomerCache(newSuggestions); - } - } catch (error) { - console.error('Failed to fetch customer suggestions:', error) - } - }, - - updateCustomerCache(newCustomers) { - const merged = [...this.cachedCustomers]; - newCustomers.forEach(newCust => { - const index = merged.findIndex(c => c.name.toLowerCase() === newCust.name.toLowerCase()); - if (index === -1) { - merged.push(newCust); - } else { - // Update existing if new one has real hashkey - if (!merged[index].hashkey.startsWith('new-') || newCust.hashkey) { - merged[index] = newCust; - } - } - }); - - // Limit cache to 200 most recent - this.cachedCustomers = merged.slice(-200); - localStorage.setItem('pos_cached_customers', JSON.stringify(this.cachedCustomers)); - }, - - async fetchPosSessions(storeHash, page = 1) { - this.loading = true - try { - const response = await axios.post('/api/pos/sessions/list', { - store_hash: storeHash, - page: page, - per_page: 10 - }) - if (response.data && response.data.success) { - const { sessions, total_count, page: currentPage } = response.data.data - if (page === 1) { - this.posSessions = sessions - } else { - // Avoid duplicates - const existingHashes = new Set(this.posSessions.map(s => s.hashkey)) - const newSessions = sessions.filter(s => !existingHashes.has(s.hashkey)) - this.posSessions = [...this.posSessions, ...newSessions] - } - this.posSessionsCount = total_count - this.posSessionsPage = currentPage - } - } catch (error) { - console.error('Failed to fetch POS sessions:', error) - this.error = 'Failed to load POS history' - } finally { - this.loading = false - } - }, - - syncFromSSE(data) { - // 1. Sync Today's Stats - if (data.pos_stats) { - // Merge into existing stats to preserve store_name/photo if SSE payload is partial - this.todayStats = { - ...this.todayStats, - ...data.pos_stats - }; - } - - // 2. Sync Customers (Merge into search suggestions and persistent cache) - if (data.customers && data.customers.length > 0) { - this.updateCustomerCache(data.customers); - - // If currently showing recent customers, refresh the view - if (!this.loading && this.customerSuggestions.length > 0) { - // Minor delay to avoid flickering if user is typing - const recent = this.cachedCustomers.slice(-3).reverse(); - this.customerSuggestions = recent; - } - } - - // 3. Sync Inventory Deltas (Stock/Price changes + newly assigned products) - if (data.inventory_deltas && data.inventory_deltas.length > 0) { - let hasNewProduct = false; - data.inventory_deltas.forEach(delta => { - const index = this.products.findIndex(p => p.hashkey === delta.hashkey || p.id === delta.id); - if (index !== -1) { - this.products[index] = { - ...this.products[index], - ...delta - }; - } else if (this.products.length > 0) { - // Product not in list — newly assigned to this store - hasNewProduct = true; - } - }); - // Trigger a full reload so store-specific price/available are fetched correctly. - // Requires an active session so the backend resolves the correct store. - if (hasNewProduct && this.activeSession) { - this.fetchProducts(); - } - } - } - } -}) - diff --git a/resources/js/stores/product.js b/resources/js/stores/product.js deleted file mode 100644 index b0446b1..0000000 --- a/resources/js/stores/product.js +++ /dev/null @@ -1,218 +0,0 @@ -import { defineStore } from 'pinia' -import axios from 'axios' -import { useOPFS } from '../composables/useOPFS' - -const opfs = useOPFS() -const CACHE_KEY_PRODUCTS = 'cached_products.json' - -export const useProductStore = defineStore('product', { - state: () => ({ - products: [], - currentProduct: null, - categories: [], - subcategories: [], - loading: false, - error: null, - detailsCache: {} // Cache for full product details keyed by hashkey - }), - actions: { - async fetchProducts(force = false) { - if (!force && this.products.length > 0) { - return; - } - - this.loading = true - this.error = null - - // Attempt to load from cache first for immediate UI update - try { - const cachedProducts = await opfs.loadJSON(CACHE_KEY_PRODUCTS) - if (cachedProducts && Array.isArray(cachedProducts)) { - this.products = cachedProducts - } - } catch (e) { - console.warn('Failed to load products from cache:', e) - } - - try { - const response = await axios.post('/Market/Products/List') - const newProducts = response.data || [] - - // Merge or replace - this.products = newProducts - - // Save to cache - await opfs.saveJSON(CACHE_KEY_PRODUCTS, this.products) - } catch (error) { - console.error('Failed to fetch products:', error) - this.error = error.message - } finally { - this.loading = false - } - }, - - syncFromSSE(data) { - // 1. Full Market List - if (data.products_market && data.products_market.length > 0) { - console.log('[ProductStore] Syncing full market list from SSE'); - this.products = data.products_market; - // Optionally cache to OPFS - opfs.saveJSON(CACHE_KEY_PRODUCTS, this.products).catch(() => {}); - } - - // 2. Deltas (Inventory/Price/Details) - if (data.inventory_deltas && data.inventory_deltas.length > 0) { - data.inventory_deltas.forEach(delta => { - const index = this.products.findIndex(p => p.hashkey === delta.hashkey); - if (index !== -1) { - this.products[index] = { - ...this.products[index], - ...delta - }; - } else { - // New product added - this.products.unshift(delta); - } - - // Also update currentProduct if it matches - if (this.currentProduct && (this.currentProduct.hashkey === delta.hashkey)) { - this.currentProduct = { - ...this.currentProduct, - ...delta - }; - } - }); - } - - // 3. New detailed product data from SSE (if added) - if (data.product_details && typeof data.product_details === 'object') { - Object.entries(data.product_details).forEach(([hash, details]) => { - this.detailsCache[hash] = details; - - if (this.currentProduct && this.currentProduct.hashkey === hash) { - this.currentProduct = { ...this.currentProduct, ...details }; - } - }); - } - }, - - async fetchProductById(hashkey, storeHash = null) { - if (this.detailsCache[hashkey]) { - this.currentProduct = this.detailsCache[hashkey]; - // return early but still fetch in background if needed (optional) - // for now just return it - return; - } - - this.loading = true - this.error = null - try { - const response = await axios.post('/View/Product/Details/data', { - target: hashkey, - data: { store_hash: storeHash } - }) - if (response.data && response.data.success && response.data.data) { - this.currentProduct = response.data.data - this.detailsCache[hashkey] = this.currentProduct - } - } catch (error) { - console.error('Failed to fetch product:', error) - this.error = error.message - } finally { - this.loading = false - } - }, - - async fetchCategories() { - try { - const response = await axios.get('/api/categories') - this.categories = response.data || [] - } catch (error) { - console.error('Failed to fetch categories:', error) - } - }, - - async fetchSubcategories(categoryId) { - try { - const response = await axios.get(`/api/subcategories?category_id=${categoryId}`) - this.subcategories = response.data || [] - } catch (error) { - console.error('Failed to fetch subcategories:', error) - } - }, - - async createProduct(data) { - this.loading = true - this.error = null - try { - const response = await axios.post('/api/products', data) - if (response.data && response.data.success) { - this.products.push(response.data.product) - } - return response.data - } catch (error) { - console.error('Failed to create product:', error) - this.error = error.message - throw error - } finally { - this.loading = false - } - }, - - async updateProduct(id, data) { - this.loading = true - this.error = null - try { - const response = await axios.put(`/api/products/${id}`, data) - if (response.data && response.data.success) { - const index = this.products.findIndex(p => p.id === id) - if (index !== -1) { - this.products[index] = response.data.product - } - } - return response.data - } catch (error) { - console.error('Failed to update product:', error) - this.error = error.message - throw error - } finally { - this.loading = false - } - }, - - async deleteProduct(id) { - this.loading = true - this.error = null - try { - const response = await axios.delete(`/api/products/${id}`) - if (response.data && response.data.success) { - this.products = this.products.filter(p => p.id !== id) - } - return response.data - } catch (error) { - console.error('Failed to delete product:', error) - this.error = error.message - throw error - } finally { - this.loading = false - } - }, - - async updateProductStatus(productId, status) { - try { - const response = await axios.post('/api/products/status', { - product_id: productId, - status: status - }) - return response.data - } catch (error) { - console.error('Failed to update product status:', error) - throw error - } - }, - - resetCurrentProduct() { - this.currentProduct = null - } - } -}) \ No newline at end of file diff --git a/resources/js/stores/store.js b/resources/js/stores/store.js deleted file mode 100644 index 9ab1d75..0000000 --- a/resources/js/stores/store.js +++ /dev/null @@ -1,160 +0,0 @@ -import { defineStore } from 'pinia' -import axios from 'axios' -import { useOPFS } from '../composables/useOPFS' - -const opfs = useOPFS() -const CACHE_KEY_STORES = 'cached_stores.json' - -export const useStoreStore = defineStore('store', { - state: () => ({ - stores: [], - currentStore: null, - storeProducts: [], - loading: false, - error: null - }), - actions: { - async fetchStores() { - this.loading = true - this.error = null - - // Load from cache first - try { - const cachedStores = await opfs.loadJSON(CACHE_KEY_STORES) - if (cachedStores && Array.isArray(cachedStores)) { - this.stores = cachedStores - } - } catch (e) { - console.warn('Failed to load stores from cache:', e) - } - - try { - const response = await axios.get('/api/stores') - this.stores = response.data || [] - - // Save to cache - await opfs.saveJSON(CACHE_KEY_STORES, this.stores) - } catch (error) { - console.error('Failed to fetch stores:', error) - this.error = error.message - } finally { - this.loading = false - } - }, - - async fetchStoreByHash(hash) { - this.loading = true - this.error = null - try { - const response = await axios.get(`/api/stores/${hash}`) - if (response.data && response.data.success) { - this.currentStore = response.data.store - } - } catch (error) { - console.error('Failed to fetch store:', error) - this.error = error.message - } finally { - this.loading = false - } - }, - - async createStore(data) { - this.loading = true - this.error = null - try { - const response = await axios.post('/api/stores', data) - if (response.data && response.data.success) { - this.stores.push(response.data.store) - } - return response.data - } catch (error) { - console.error('Failed to create store:', error) - this.error = error.message - throw error - } finally { - this.loading = false - } - }, - - async updateStore(id, data) { - this.loading = true - this.error = null - try { - const response = await axios.put(`/api/stores/${id}`, data) - if (response.data && response.data.success) { - const index = this.stores.findIndex(s => s.id === id) - if (index !== -1) { - this.stores[index] = response.data.store - } - } - return response.data - } catch (error) { - console.error('Failed to update store:', error) - this.error = error.message - throw error - } finally { - this.loading = false - } - }, - - async deleteStore(id) { - this.loading = true - this.error = null - try { - const response = await axios.delete(`/api/stores/${id}`) - if (response.data && response.data.success) { - this.stores = this.stores.filter(s => s.id !== id) - } - return response.data - } catch (error) { - console.error('Failed to delete store:', error) - this.error = error.message - throw error - } finally { - this.loading = false - } - }, - - async fetchStoreProducts(storeId) { - try { - const response = await axios.get(`/api/stores/${storeId}/products`) - this.storeProducts = response.data || [] - return this.storeProducts - } catch (error) { - console.error('Failed to fetch store products:', error) - throw error - } - }, - - async addProductToStore(storeId, productId, quantity, price) { - try { - const response = await axios.post(`/api/stores/${storeId}/products`, { - product_id: productId, - quantity: quantity, - price: price - }) - return response.data - } catch (error) { - console.error('Failed to add product to store:', error) - throw error - } - }, - - async removeProductFromStore(storeId, productId) { - try { - const response = await axios.delete(`/api/stores/${storeId}/products/${productId}`) - if (response.data && response.data.success) { - this.storeProducts = this.storeProducts.filter(p => p.product_id !== productId) - } - return response.data - } catch (error) { - console.error('Failed to remove product from store:', error) - throw error - } - }, - - resetCurrentStore() { - this.currentStore = null - } - } -}) \ No newline at end of file diff --git a/resources/js/utils/UserTypes.js b/resources/js/utils/UserTypes.js index e9eec45..8257334 100644 --- a/resources/js/utils/UserTypes.js +++ b/resources/js/utils/UserTypes.js @@ -1,19 +1,52 @@ export const UserTypes = { - ULTIMATE: 'ult', - SUPER_OPERATOR: 'super operator', - OPERATOR: 'operator', - COORDINATOR: 'coordinator', - COOP_OFFICER: 'coop officer', - COOP_MEMBER: 'coop member', - SUPPLIER_OVERSEER: 'supplier overseer', - WHOLESALE_BUYER: 'wholesale buyer', - SUPPLIER: 'supplier', - STORE_OWNER: 'store owner', - STORE_MANAGER: 'store manager', - USER: 'user', - RIDER: 'rider', - AUDIT: 'audit', - POS_TERMINAL: 'pos terminal', - ANY_USER: 'default', - PUBLIC: 'public' + SUPER_ADMIN: 'super_admin', + PUNONG_BARANGAY: 'punong_barangay', + KAGAWAD: 'kagawad', + SECRETARY: 'secretary', + TREASURER: 'treasurer', + SK_CHAIRPERSON: 'sk_chairperson', + SK_COUNCILOR: 'sk_councilor', + TANOD: 'tanod', + BHW: 'bhw', + DAYCARE_WORKER: 'daycare_worker', + STAFF: 'staff', + RESIDENT: 'resident', + AUDIT: 'audit', + ANY_USER: 'default', + PUBLIC: 'public', }; + +export const UserTypeLabels = { + [UserTypes.SUPER_ADMIN]: 'System Administrator', + [UserTypes.PUNONG_BARANGAY]: 'Punong Barangay', + [UserTypes.KAGAWAD]: 'Barangay Kagawad', + [UserTypes.SECRETARY]: 'Barangay Secretary', + [UserTypes.TREASURER]: 'Barangay Treasurer', + [UserTypes.SK_CHAIRPERSON]: 'SK Chairperson', + [UserTypes.SK_COUNCILOR]: 'SK Councilor', + [UserTypes.TANOD]: 'Barangay Tanod', + [UserTypes.BHW]: 'Barangay Health Worker', + [UserTypes.DAYCARE_WORKER]: 'Daycare Worker', + [UserTypes.STAFF]: 'Staff', + [UserTypes.RESIDENT]: 'Resident', + [UserTypes.AUDIT]: 'Auditor', + [UserTypes.PUBLIC]: 'Public / Guest', +}; + +export const ADMIN_ROLES = [ + UserTypes.SUPER_ADMIN, + UserTypes.PUNONG_BARANGAY, + UserTypes.SECRETARY, + UserTypes.TREASURER, + UserTypes.KAGAWAD, +]; + +export const STAFF_ROLES = [ + ...ADMIN_ROLES, + UserTypes.SK_CHAIRPERSON, + UserTypes.SK_COUNCILOR, + UserTypes.TANOD, + UserTypes.BHW, + UserTypes.DAYCARE_WORKER, + UserTypes.STAFF, +]; diff --git a/routes/web.php b/routes/web.php index 4c66e0a..0940126 100644 --- a/routes/web.php +++ b/routes/web.php @@ -2,66 +2,44 @@ declare(strict_types=1); -use App\Enums\UserActions; -use App\Http\Controllers\Helpers\Permissions\UserPermissions; -use App\Http\Controllers\Market\CartController; -use App\Http\Controllers\Market\ProductController; -use App\Http\Controllers\Market\StoreController; -use App\Http\Controllers\Market\ShipmentController; -use App\Http\Controllers\Market\FarmerController; -use App\Http\Controllers\Pages\AccountSettingsPageController; -use App\Http\Controllers\Pages\TransferMyCreditPageController; -use App\Http\Controllers\Pages\UserModifyAdminPageController; -use App\Http\Controllers\Pages\UserListPageController; -use App\Http\Controllers\Photos\PhotoGallery; -use App\Http\Controllers\UserManagement\CreateUserControllerUltimate; -use App\Models\Market\Store; -use Hyperf\Codec\Json; -use Hypervel\Support\Facades\Route; -use Hypervel\Support\Facades\Auth; -use App\Http\Controllers\LoginController; - -use App\Models\User; - -use Hypervel\Support\Facades\Cache; - -use Hypervel\Support\Facades\Config; - -use Hypervel\Support\Facades\Response; - use App\Enums\UserTypes; - -use Hyperf\Redis\RedisFactory; -use Hypervel\Support\Facades\Storage; -use Psr\Http\Message\ResponseInterface; - -use Hypervel\Support\Facades\Session; - -use Hypervel\Http\Request; -use App\Http\Controllers\viewHelperController; - -use Hypervel\Support\Facades\File; -use App\Http\Controllers\PageMemoryController; -use App\Http\Controllers\FilesMainController; - -use App\Models\FileContent; -use routes\Market\Web_stores; -use routes\Web_debug; -use routes\Web_files; -use App\Http\Controllers\Support\Inertia; -use App\Http\Controllers\Support\VueRouteMap; +use App\Http\Controllers\Auth\LoginController; +use App\Http\Controllers\Admin\AdminConsoleController; +use App\Http\Controllers\Admin\ApiTokenController; +use App\Http\Controllers\Admin\LandingPageController; +use App\Http\Controllers\Admin\SystemSettingsController; +use App\Http\Controllers\Admin\UserController; +use App\Http\Controllers\Barangay\BlotterController; +use App\Http\Controllers\Barangay\BlotterHearingController; +use App\Http\Controllers\Barangay\BudgetController; +use App\Http\Controllers\Barangay\DocumentRequestController; +use App\Http\Controllers\Barangay\HouseholdController; +use App\Http\Controllers\Barangay\ProjectController; +use App\Http\Controllers\Barangay\RequestTypeController; +use App\Http\Controllers\Barangay\ResidentController; +use App\Http\Controllers\ChapterController; +use App\Http\Controllers\FileController; +use App\Http\Controllers\Payment\QRPHController; +use App\Http\Controllers\PwaManifestController; use App\Http\Controllers\Support\AnnouncementController; +use App\Http\Controllers\Support\Inertia; use App\Http\Controllers\Support\SSEController; -use App\Http\Controllers\Property\PropertyManagementController; -use App\Http\Controllers\Accounting\AccountingController; -use App\Http\Controllers\Subscription\SubscriptionPlanController; -use App\Http\Controllers\Subscription\SubscriptionController; +use App\Http\Controllers\Support\VueRouteMap; +use App\Models\User; +use Hypervel\Support\Facades\Auth; +use Hypervel\Support\Facades\Redis; +use Hypervel\Support\Facades\Response; +use Hypervel\Support\Facades\Route; VueRouteMap::registerRoutes(); +// ───────────────────────────────────────────────────────────────────────────── +// Health +// ───────────────────────────────────────────────────────────────────────────── + Route::get('/health', function () { $checks = []; - $allOk = true; + $allOk = true; try { \Hypervel\Support\Facades\DB::select('SELECT 1'); @@ -72,859 +50,268 @@ Route::get('/health', function () { } try { - \Hypervel\Support\Facades\Redis::ping(); + Redis::ping(); $checks['redis'] = 'ok'; } catch (\Throwable $e) { $checks['redis'] = 'fail: ' . $e->getMessage(); $allOk = false; } - if ($allOk) { - return 'OK'; - } - - return response()->json(['status' => 'degraded', 'checks' => $checks], 503); + return $allOk ? 'OK' : response()->json(['status' => 'degraded', 'checks' => $checks], 503); }); -Route::get('/manifest.json', [\App\Http\Controllers\PwaManifestController::class, 'manifest']); +Route::get('/manifest.json', [PwaManifestController::class, 'manifest']); -// Route::get('/', function () { -// return view('layouts.default'); -// }, ['middleware' => 'auth']); - -// Route::get('/app', function () { -// $page = Inertia::render('Home'); -// return view('layouts.application-layout', ['page' => $page]); -// }, ['middleware' => ['web', 'auth']]); - - -Route::get('/home-data', function () { - $userCount = \App\Models\User::count(); - $storeCount = \App\Models\Market\Store::where('is_active', true)->count(); - $pendingOrders = \App\Models\Market\PosSession::where('status', 'PENDING')->count(); - - /** @var \App\Models\User $user */ - $user = Auth::user(); - $personalBalance = $user->total_balance ?? 0; - - $totalTransactionsNo = \App\Models\GlobalTransaction::count(); - $totalTransactionsPhp = \App\Models\GlobalTransaction::sum('amount'); - $projectedIncomeToday = \App\Models\GlobalTransaction::whereDate('created_at', today()) - ->where('flow', \App\Enums\Market\TransactionFlow::INCOME->value) - ->sum('amount'); - - // Store-scoped today metrics (for STORE_OWNER / STORE_MANAGER dashboards). - $acctType = $user?->acct_type instanceof \App\Enums\UserTypes - ? $user->acct_type - : \App\Enums\UserTypes::tryFrom($user?->acct_type ?? ''); - $isBig3 = in_array($acctType, [ - \App\Enums\UserTypes::ULTIMATE, - \App\Enums\UserTypes::SUPER_OPERATOR, - \App\Enums\UserTypes::OPERATOR, - ], true); - - $todayTxQuery = \App\Models\GlobalTransaction::whereDate('created_at', today()); - if (!$isBig3 && $user) { - $storeIds = \App\Models\Market\Store::where('owner_id', $user->id) - ->orWhereHas('managers', fn ($q) => $q->where('user_id', $user->id)) - ->pluck('id'); - $todayTxQuery->whereIn('store_id', $storeIds); - } - $transactionsTodayNo = (clone $todayTxQuery)->count(); - $cashFlowToday = (clone $todayTxQuery) - ->selectRaw('COALESCE(SUM(amount * flow), 0) as net') - ->value('net'); - $myStoresCount = !$isBig3 && $user - ? \App\Models\Market\Store::where('owner_id', $user->id) - ->orWhereHas('managers', fn ($q) => $q->where('user_id', $user->id)) - ->count() - : $storeCount; - - $cooperativeTotalNo = 0; - $cooperativeMembersNo = 0; - $myCooperativesNo = 0; - $pendingMembersNo = 0; - if ($acctType === \App\Enums\UserTypes::COORDINATOR && $user) { - $cooperativeTotalNo = \App\Models\Market\Organization::where('type', 'COOPERATIVE')->count(); - $cooperativeMembersNo = \App\Models\Market\CooperativeMember::whereHas('organization', fn ($q) => $q->where('created_by', $user->id))->count(); - $myCooperativesNo = \App\Models\Market\Organization::where('type', 'COOPERATIVE')->where('created_by', $user->id)->withCount('members')->count(); - $myCoopIds = \App\Models\Market\Organization::where('type', 'COOPERATIVE')->where('created_by', $user->id)->pluck('id'); - $pendingMembersNo = \App\Models\Market\CooperativeMember::whereIn('organization_id', $myCoopIds)->where('created_at', '>=', now()->subDays(7))->count(); - } - - // Cooperative chapter dashboards (officer / member). - $chapterInfo = null; - $chapterMemberCount = 0; - $childChapterCount = 0; - $newMembers7d = 0; - if ($acctType === \App\Enums\UserTypes::COOP_OFFICER && $user) { - $myChapterMember = \App\Models\ChapterMember::join('chapters', 'chapters.id', 'chapter_members.chapter_id') - ->where('chapter_members.user_id', $user->id)->where('chapter_members.is_active', true) - ->orderByRaw("FIELD(chapters.level,'national','region','province','city','municipal','barangay')") - ->select('chapter_members.*', 'chapters.id as chapter_id_resolved', 'chapters.name as chapter_name', 'chapters.level as chapter_level', 'chapters.hashkey as chapter_hashkey', 'chapters.cooperative_id as chapter_coop_id') - ->first(); - if ($myChapterMember) { - $myChapterId = (int) $myChapterMember->chapter_id_resolved; - $chapterMemberCount = \App\Models\ChapterMember::where('chapter_id', $myChapterId)->where('is_active', true)->count(); - $childChapterCount = \App\Models\Chapter::where('parent_id', $myChapterId)->where('is_active', true)->count(); - $newMembers7d = \App\Models\ChapterMember::where('chapter_id', $myChapterId)->where('is_active', true)->where('created_at', '>=', now()->subDays(7))->count(); - $cooperativeHash = $myChapterMember->chapter_coop_id - ? \App\Models\Market\Organization::where('id', $myChapterMember->chapter_coop_id)->value('hashkey') - : null; - $chapterInfo = [ - 'chapter_name' => $myChapterMember->chapter_name, - 'chapter_level' => $myChapterMember->chapter_level, - 'chapter_hashkey' => $myChapterMember->chapter_hashkey, - 'cooperative_hash' => $cooperativeHash, - ]; - } - } - if ($acctType === \App\Enums\UserTypes::COOP_MEMBER && $user) { - $myChapterMember = \App\Models\ChapterMember::join('chapters', 'chapters.id', 'chapter_members.chapter_id') - ->where('chapter_members.user_id', $user->id)->where('chapter_members.is_active', true) - ->orderByRaw("FIELD(chapters.level,'barangay','city','municipal','province','region','national')") - ->select('chapter_members.*', 'chapters.id as chapter_id_resolved', 'chapters.name as chapter_name', 'chapters.level as chapter_level', 'chapters.cooperative_id as chapter_coop_id') - ->first(); - if ($myChapterMember) { - $myChapterId = (int) $myChapterMember->chapter_id_resolved; - $chapterMemberCount = \App\Models\ChapterMember::where('chapter_id', $myChapterId)->where('is_active', true)->count(); - $cooperativeName = $myChapterMember->chapter_coop_id - ? \App\Models\Market\Organization::where('id', $myChapterMember->chapter_coop_id)->value('name') - : null; - $chapterInfo = [ - 'chapter_name' => $myChapterMember->chapter_name, - 'chapter_level' => $myChapterMember->chapter_level, - 'cooperative_name' => $cooperativeName, - 'member_count' => $chapterMemberCount, - ]; - } - } - - $isOperator = $acctType === \App\Enums\UserTypes::OPERATOR; - $operatorStoreIds = []; - if ($isOperator && $user) { - $operatorStoreIds = \App\Models\Market\Store::where('owner_id', $user->id) - ->orWhereHas('managers', fn($q) => $q->where('user_id', $user->id)) - ->pluck('id')->toArray(); - } - $activePosSessionsNo = $isOperator - ? \App\Models\Market\PosSession::whereIn('store_id', $operatorStoreIds)->where('status', 'PENDING')->count() - : \App\Models\Market\PosSession::where('status', 'PENDING')->count(); - $managedStoresNo = $isOperator ? count($operatorStoreIds) : $storeCount; - $todayRevenueOperator = $isOperator - ? \App\Models\GlobalTransaction::whereDate('created_at', today()) - ->whereIn('store_id', $operatorStoreIds) - ->where('flow', \App\Enums\Market\TransactionFlow::INCOME->value) - ->sum('amount') - : $projectedIncomeToday; - - $props = [ - 'props' => [ - 'user' => $user, - 'stats' => [ - 'total_users_no' => $userCount, - 'active_stores_no' => $storeCount, - 'pending_orders_no' => $pendingOrders, - 'total_balance_php' => number_format((float) $personalBalance, 2), - 'total_transactions_no' => $totalTransactionsNo, - 'total_transactions_php' => number_format((float) $totalTransactionsPhp, 2), - 'projected_income_today' => number_format((float) $projectedIncomeToday, 2), - 'transactions_today_no' => $transactionsTodayNo, - 'cash_flow_today_php' => number_format((float) $cashFlowToday, 2), - 'my_stores_no' => $myStoresCount, - 'cooperative_total_no' => $cooperativeTotalNo, - 'cooperative_members_no' => $cooperativeMembersNo, - 'my_cooperatives_no' => $myCooperativesNo, - 'pending_members_no' => $pendingMembersNo, - 'active_pos_sessions_no' => $activePosSessionsNo, - 'managed_stores_no' => $managedStoresNo, - 'today_revenue_php' => number_format((float) $todayRevenueOperator, 2), - 'chapter_member_count' => $chapterMemberCount, - 'child_chapter_count' => $childChapterCount, - 'new_members_7d' => $newMembers7d, - ], - ], - ]; - - if ($chapterInfo !== null) { - $props['props']['chapter_info'] = $chapterInfo; - } - - return response()->json($props); -}); - -// Route::get('/{page}', [viewHelperController::class, 'servePageFragment'], ['middleware' => 'auth']); - - - - -Route::get('/p/{page}/s/{data}', [viewHelperController::class, 'servePageFragmentWithTemplate'], ['middleware' => 'auth']); -Route::get('/p/{page}/s/', [viewHelperController::class, 'servePageFragmentWithTemplate'], ['middleware' => 'auth']); +// ───────────────────────────────────────────────────────────────────────────── +// Auth +// ───────────────────────────────────────────────────────────────────────────── Route::get('/login', function (\Hypervel\Http\Request $request) { - if (Auth::check() && !$request->has('logged_out')) { - return redirect('/'); - } + if (Auth::check() && !$request->has('logged_out')) return redirect('/'); $page = Inertia::render('Auth.Login', []); return view('layouts.application-layout', compact('page')); }, ['middleware' => 'web']); -Route::get('/sp/login', function (\Hypervel\Http\Request $request) { - if (Auth::check() && !$request->has('logged_out')) { - return redirect('/'); - } - $page = Inertia::render('Auth.Login', []); - return view('layouts.application-layout', compact('page')); -}, ['middleware' => 'web']); - -Route::get('/old/login', function () { - return view('login'); -}, ['middleware' => 'web']); - -Route::get('/get/isloggedin', function () { - $isLoggedIn = Auth::check(); - if ($isLoggedIn) { - $user = Auth::user(); - if (!$user || !$user->active) { - Auth::logout(); - if (session() && method_exists(session(), 'flush')) { - session()->flush(); - } - return Response::json(['isloggedin' => false]); - } - - // Check Redis forced_logout flag (set by admin remote-logout) - $hashkey = $user->hashkey ?? null; - if ($hashkey && \Hypervel\Support\Facades\Redis::get("forced_logout:{$hashkey}")) { - \Hypervel\Support\Facades\Redis::del("forced_logout:{$hashkey}"); - Auth::logout(); - if (session() && method_exists(session(), 'flush')) { - session()->flush(); - } - return Response::json(['isloggedin' => false]); - } - } - return Response::json(['isloggedin' => $isLoggedIn]); -}, ['middleware' => 'web']); - -Route::get('/sse/stream', [SSEController::class, 'stream'], ['middleware' => 'auth']); - - -Route::get('/get/user/acct-type', function () { - $user = Auth::user(); - return Response::json([ - 'acct_type' => $user->acct_type, - ]); -}, ['middleware' => 'auth']); - - - - -Route::get('/get/isExec', function () { - $execCommand = Auth::user()->exec_command ?? ''; - $user = User::findOrFail(Auth::id()); - $user->exec_command = ''; - $user->save(); - return Response::raw($execCommand); -}, ['middleware' => 'auth']); - - Route::post('/post/loginnow', [LoginController::class, 'authenticate'], ['middleware' => 'web']); Route::get('/session/extend', [LoginController::class, 'extendcurrentSession'], ['middleware' => 'auth']); -Route::get('/sp/listviews', function () { - return Response::json(viewHelperController::getAllViews()); -}, ['middleware' => 'ultimate']); - -Route::get('/debug/asset', function () { - -}, ['middleware' => 'auth']); - -Route::get('/debug/assetview', function () { - return view('adminlte'); -}, ['middleware' => 'auth']); - - - -Route::get('/account_settings/details', [AccountSettingsPageController::class, 'listDetails'], ['middleware' => ['web', 'auth']]); -Route::post('/User/Settings/Details', [AccountSettingsPageController::class, 'listSettings'], ['middleware' => ['web', 'auth']]); -Route::post('/user/changemypassword', [AccountSettingsPageController::class, 'changepassword'], ['middleware' => ['web', 'auth']]); -Route::post('/user/updatephoto', [AccountSettingsPageController::class, 'updatePhoto'], ['middleware' => ['web', 'auth']]); -Route::post('/User/Settings/Run/Scripts', [AccountSettingsPageController::class, 'listRunScripts'], ['middleware' => ['web', 'auth']]); - -Route::get('/user/note/content', [AccountSettingsPageController::class, 'getUserNotes'], ['middleware' => ['web', 'auth']]); -Route::get('/user/note/dismiss', [AccountSettingsPageController::class, 'clearUserNotes'], ['middleware' => ['web', 'auth']]); - -Route::get('/go/logoutnow', [AccountSettingsPageController::class, 'logoutnow'], ['middleware' => 'auth']); -Route::get('/logout', [AccountSettingsPageController::class, 'logoutnow'], ['middleware' => 'auth']); - -Route::get('/admin/users/list', [UserListPageController::class, 'Response_ListChildrenofCurrentUser'], ['middleware' => 'auth']); -Route::post('/admin/user/details', [UserModifyAdminPageController::class, 'Response_UserDetails'], ['middleware' => 'auth']); -Route::post('/user/details/children', [UserModifyAdminPageController::class, 'Response_childrenofTargetUser'], ['middleware' => 'auth']); -Route::post('/admin/user/children/direct', [UserModifyAdminPageController::class, 'Response_directChildrenofTargetUser'], ['middleware' => 'auth']); - - -Route::post('/admin/user/disable', [UserModifyAdminPageController::class, 'Response_DisableUser'], ['middleware' => 'auth']); -Route::post('/admin/user/enable', [UserModifyAdminPageController::class, 'Response_EnableUser'], ['middleware' => 'auth']); -Route::post('/admin/user/delete', [UserModifyAdminPageController::class, 'Response_DeleteUser'], ['middleware' => 'auth']); - -Route::post('/admin/user/note/content', [UserModifyAdminPageController::class, 'Response_ViewNotes'], ['middleware' => 'auth']); -Route::post('/admin/user/note/delete', [UserModifyAdminPageController::class, 'Response_DeleteNotes'], ['middleware' => 'auth']); -Route::post('/admin/user/note/update', [UserModifyAdminPageController::class, 'Response_ReplaceNotes'], ['middleware' => 'auth']); - -Route::post('/admin/user/exec/content', [UserModifyAdminPageController::class, 'Response_ViewExec'], ['middleware' => 'auth']); -Route::post('/admin/user/exec/update', [UserModifyAdminPageController::class, 'Response_ReplaceExec'], ['middleware' => 'auth']); -Route::post('/admin/user/exec/delete', [UserModifyAdminPageController::class, 'Response_DeleteExec'], ['middleware' => 'auth']); - - -Route::post('/admin/user/details/update', [UserModifyAdminPageController::class, 'Response_UpdateUserDetails'], ['middleware' => 'auth']); -Route::post('/admin/user/password/reset', [UserModifyAdminPageController::class, 'Response_ResetUserPassword'], ['middleware' => 'auth']); -Route::post('/admin/logout/force/user', [UserModifyAdminPageController::class, 'Response_LogoutUser'], ['middleware' => 'auth']); -Route::post('/admin/user/roles/get', [UserModifyAdminPageController::class, 'Response_UserRoles'], ['middleware' => 'auth']); -Route::post('/admin/user/store/detach', [UserModifyAdminPageController::class, 'Response_DetachStore'], ['middleware' => 'auth']); -Route::post('/admin/user/roles/all', [UserModifyAdminPageController::class, 'Response_AllRoles'], ['middleware' => 'auth']); -Route::post('/admin/user/roles/change', [UserModifyAdminPageController::class, 'Response_ChangeUserRoles'], ['middleware' => 'auth']); -Route::post('/admin/user/session/extend', [UserModifyAdminPageController::class, 'Response_ExtendUserSessions'], ['middleware' => 'auth']); - -Route::post('/admin/list/usertype/create', [CreateUserControllerUltimate::class, 'listAllUserTypesforSelectHTML'], ['middleware' => 'auth']); -Route::post('/admin/user/list/numbers/hash', [CreateUserControllerUltimate::class, 'listAllUsersforParentSelectHTML'], ['middleware' => 'auth']); -Route::post('/admin/user/number/exists', [CreateUserControllerUltimate::class, 'checkIfUserMobileNumberExists'], ['middleware' => 'auth']); -Route::post('/admin/user/username/exists', [CreateUserControllerUltimate::class, 'checkIfUsernameExists'], ['middleware' => 'auth']); -Route::post('/admin/user/create', [CreateUserControllerUltimate::class, 'CreateUser'], ['middleware' => 'auth']); - - - -Route::post('/user/sendmycredit', [TransferMyCreditPageController::class, 'Response_TransferMyCredit'], ['middleware' => 'auth']); - - -// ── Subscriptions (user-facing) ────────────────────────────────────────── -Route::get('/subscription/plans', [SubscriptionController::class, 'listAvailablePlans'], ['middleware' => 'auth']); -Route::get('/subscription/my', [SubscriptionController::class, 'mySubscription'], ['middleware' => 'auth']); -Route::get('/subscription/invoices', [SubscriptionController::class, 'myInvoices'], ['middleware' => 'auth']); -Route::post('/subscription/pay/wallet', [SubscriptionController::class, 'payWithWallet'], ['middleware' => 'auth']); - -// ── Subscription Plans (admin/ultimate only) ───────────────────────────── -Route::get('/admin/subscription/plans', [SubscriptionPlanController::class, 'listPlans'], ['middleware' => 'ult']); -Route::post('/admin/subscription/plan/create', [SubscriptionPlanController::class, 'createPlan'], ['middleware' => 'ult']); -Route::post('/admin/subscription/plan/update', [SubscriptionPlanController::class, 'updatePlan'], ['middleware' => 'ult']); -Route::post('/admin/subscription/plan/toggle', [SubscriptionPlanController::class, 'togglePlan'], ['middleware' => 'ult']); -Route::post('/admin/subscription/list', [SubscriptionPlanController::class, 'listAllSubscriptions'], ['middleware' => 'ult']); - - -Route::get('/test/upload', function () { - $cache = Cache::get('querycache:5324fb1ee26f771b9d87ae56f822bd99++++85838d9513d840ff389e75c60d7c4a40'); - // $fileData = Storage::get('test/images.jpg'); - return Response::json($cache); - // $title = 'Test FIle Now'; -// $description = 'This is a test file list entry'; -// $details = ['status' => 'inves', 'public' => false]; -// $categories = 'product,images'; -// $tags = 'test,product,images'; - - - - // $data = FilesMainController::uploadFileList( -// $fileData, -// $title, -// $filename ?? '', -// $description, -// $details, -// $categories, -// $tags -// ); - - // $filelist_hash = '5ae2d383-4156-4afd-95aa-e6d0984aa118Ks6oZlcvLVKtwvPX9QkrhOE72zK0XKh7MXnZeQ81HOjUbuI6c8Q4E7noQvth5UzVSAeO8E76Y9Wdm2s3Jiiyl10BmjslkYjg7mr3'; -// return FilesMainController::viewFilebyFileListHash( $filelist_hash); - - // return $data; -}, ['middleware' => 'auth']); - - -Route::get('/test/viewfile', function () { - $file = FileContent::first(); - - if (!$file) { - abort(404, 'File not found'); +Route::get('/get/isloggedin', function () { + if (Auth::check()) { + $user = Auth::user(); + if (!$user || !$user->active) { + Auth::logout(); + session()?->flush(); + return Response::json(['isloggedin' => false]); + } + $hashkey = $user->hashkey ?? null; + if ($hashkey && Redis::get("forced_logout:{$hashkey}")) { + Redis::del("forced_logout:{$hashkey}"); + Auth::logout(); + session()?->flush(); + return Response::json(['isloggedin' => false]); + } } - $content = $file->content; + return Response::json(['isloggedin' => Auth::check()]); +}, ['middleware' => 'web']); - if (is_resource($content)) { - $content = stream_get_contents($content); - } - - return Response::make($content, 200, [ - 'Content-Type' => 'application/octet-stream', - 'Content-Disposition' => 'inline; filename="file.bin"', - 'Content-Length' => strlen($content), - ]); +Route::get('/get/user/acct-type', function () { + return Response::json(['acct_type' => Auth::user()->acct_type]); }, ['middleware' => 'auth']); +Route::get('/logout', function () { + Auth::logout(); + session()?->flush(); + return redirect('/login?logged_out=1'); +}, ['middleware' => 'auth']); +Route::get('/go/logoutnow', function () { + Auth::logout(); + session()?->flush(); + return redirect('/login?logged_out=1'); +}, ['middleware' => 'auth']); +// ───────────────────────────────────────────────────────────────────────────── +// SSE +// ───────────────────────────────────────────────────────────────────────────── +Route::get('/sse/stream', [SSEController::class, 'stream'], ['middleware' => 'auth']); +// ───────────────────────────────────────────────────────────────────────────── +// Home / Dashboard +// ───────────────────────────────────────────────────────────────────────────── -Route::post('/Products/Admin/New/', [ - 'as' => 'admin.products.new', - 'uses' => ProductController::class . '@createNew_Admin', - 'middleware' => ['auth', 'module:products'] -]); +Route::get('/home-data', function () { + $user = Auth::user(); -Route::post('/Products/Admin/FuzzySearch', [ - 'as' => 'admin.products.fuzzy.search', - 'uses' => ProductController::class . '@fuzzySearchByName', - 'middleware' => ['auth', 'module:products'] -]); + $stats = [ + 'total_users' => User::count(), + 'active_users' => User::where('active', true)->count(), + 'total_residents' => \Hypervel\Support\Facades\DB::table('barangay_residents')->where('is_active', true)->count(), + 'total_households' => \Hypervel\Support\Facades\DB::table('barangay_households')->where('is_active', true)->count(), + 'open_blotters' => \Hypervel\Support\Facades\DB::table('barangay_blotters')->whereIn('status', ['FILED', 'FOR_HEARING'])->count(), + 'pending_documents' => \Hypervel\Support\Facades\DB::table('barangay_document_requests')->whereIn('status', ['PENDING_PAYMENT', 'PROCESSING'])->count(), + 'total_projects' => \Hypervel\Support\Facades\DB::table('barangay_projects')->count(), + 'ongoing_projects' => \Hypervel\Support\Facades\DB::table('barangay_projects')->where('status', 'ONGOING')->count(), + ]; -Route::post('/Products/Admin/Edit/', [ - 'as' => 'admin.products.edit', - 'uses' => ProductController::class . '@editProductAdmin', - 'middleware' => ['auth', 'module:products'] -]); + return Response::json(['props' => ['user' => $user, 'stats' => $stats]]); +}, ['middleware' => 'auth']); -Route::post('/Products/Admin/Edit/Store', [ - 'as' => 'admin.products.bystore.edit', - 'uses' => ProductController::class . '@editProductAdmin', - 'middleware' => ['auth', 'module:products'] -]); +// ───────────────────────────────────────────────────────────────────────────── +// File Serving +// ───────────────────────────────────────────────────────────────────────────── +Route::get('/RequestData/File/{hash}', fn (string $hash) => FileController::viewFilebyFileListHash($hash), ['middleware' => 'auth']); +Route::post('/RequestData/File/Upload/{category}', fn (\Hypervel\Http\Request $r, string $category) => FileController::UploadFilefromRequest($r, $category), ['middleware' => 'auth']); -Route::post('/Products/Admin/AddtoStore/', [ - 'as' => 'admin.products.addtostore', - 'uses' => ProductController::class . '@AddProducttoStore', - 'middleware' => ['auth', 'module:products'] -]); - -Route::post('/Products/Admin/RemovefronStore/', [ - 'as' => 'admin.products.removefromstore', - 'uses' => ProductController::class . '@RemoveProductFromStore', - 'middleware' => ['auth', 'module:products'] -]); - - - - -Route::post('/View/Product/Details/data/withStores', [ - 'as' => 'admin.products.view.data.addstore', - 'uses' => ProductController::class . '@viewProductwithAddStoreData', - 'middleware' => 'auth' -]); - - - - - - -Route::post('/Products/New/Category/Datalist', [ - 'as' => 'admin.products.category.datalist.data', - 'uses' => ProductController::class . '@getCategories', - 'middleware' => 'auth' -]); - -Route::post('/Products/New/SubCategory/Datalist', [ - 'as' => 'admin.products.subcategory.datalist.data', - 'uses' => ProductController::class . '@getSubcategories', - 'middleware' => 'auth' -]); - - -Route::post('/View/Product/Details/data', [ - 'as' => 'admin.products.view.data', - 'uses' => ProductController::class . '@viewProductDetails', - 'middleware' => 'auth' -]); - -Route::post('/View/Product/Details/ByStore/data', [ - 'as' => 'admin.products.bystore.view.data', - 'uses' => ProductController::class . '@viewProductDetailsByStoreEdit', - 'middleware' => 'auth' -]); - - - -Route::post('/Market/Products/List', [ - 'as' => 'products.list.data', - 'uses' => ProductController::class . '@listProductsData', -]); - -Route::post('/Admin/Products/List', [ - 'as' => 'admin.products.list.data', - 'uses' => ProductController::class . '@listProducts_Admin', - 'middleware' => 'auth' -]); - -Route::post('/Admin/Product/Delete', [ - 'as' => 'admin.product.delete', - 'uses' => ProductController::class . '@deleteProduct_Admin', - 'middleware' => 'auth' -]); - -Route::post('/Admin/Product/ToggleStatus', [ - 'as' => 'admin.product.toggle_status', - 'uses' => ProductController::class . '@toggleProductStatus_Admin', - 'middleware' => 'auth' -]); - - - -Route::post('/File/Upload/{category}', [FilesMainController::class, 'UploadFilefromRequest'], ['middleware' => 'auth']); - -Route::get('/api/products/photo-search', [\App\Http\Controllers\Market\ProductPhotoSearchController::class, 'search'], ['middleware' => 'auth']); -Route::post('/api/products/photo-download', [\App\Http\Controllers\Market\ProductPhotoSearchController::class, 'download'], ['middleware' => 'auth']); - -Route::get('/RequestData/File/{filelist_hash}', [ - 'as' => 'requestdata.file.view', - 'uses' => 'App\Http\Controllers\FilesMainController@viewFilebyFileListHash', -]); - - -Route::post('/Request/Photos/{type}', [ - 'as' => 'request.photos', - 'uses' => PhotoGallery::class . '@handle', - 'middleware' => 'auth' -]); - - - -Route::post('/Store/New', [StoreController::class, 'store'], ['middleware' => ['auth', 'module:stores']]); -Route::post('/Store/AutoCreate', [StoreController::class, 'autoCreate'], ['middleware' => ['auth', 'module:stores']]); -Route::post('/Store/New/Category/Datalist', [StoreController::class, 'getCategories'], ['middleware' => ['auth', 'module:stores']]); -Route::post('/Store/New/SubCategory/Datalist', [StoreController::class, 'getSubcategories'], ['middleware' => ['auth', 'module:stores']]); - - -Route::post('/View/Store/Details/data', [ - 'as' => 'store.details', - 'uses' => StoreController::class . '@viewStoreDetails' -]); - - -Route::post('/Store/Edit', [StoreController::class, 'update'], ['middleware' => ['auth', 'module:stores']]); - -Route::post('/Edit/Store/Details/data', [ - 'as' => 'store.details.editing', - 'uses' => StoreController::class . '@editStoreDetails', - 'middleware' => ['auth', 'module:stores'] -]); - - - -Route::post('/ListStores/List/data', [ - 'as' => 'stores.list.data', - 'uses' => StoreController::class . '@listStoresActiveDataAll', - // 'middleware' => 'auth' -]); - - -Route::post('/ListStores/MyStores/data', [ - 'as' => 'stores.my.list.data', - 'uses' => StoreController::class . '@listStoresForCurrentUser', - 'middleware' => ['auth', 'module:stores'] -]); - -Route::post('/Admin/Stores/Selectable', [ - 'as' => 'admin.stores.selectable', - 'uses' => StoreController::class . '@listSelectableStoresForAddingProduct', - 'middleware' => ['auth', 'module:stores'] -]); - -Route::post('/Products/AssignToStore/', [ - 'as' => 'products.assign.to.store', - 'uses' => ProductController::class . '@AssignProductToOwnStore', - 'middleware' => ['auth', 'module:stores'] -]); - -Route::post('/Products/UnassignFromStore/', [ - 'as' => 'products.unassign.from.store', - 'uses' => ProductController::class . '@RemoveProductFromStore', - 'middleware' => ['auth', 'module:stores'] -]); - -Route::post('/Products/GlobalList', [ - 'as' => 'products.global.list', - 'uses' => ProductController::class . '@listGlobalProductsForPicker', - 'middleware' => ['auth', 'module:stores'] -]); - -Route::post('/Products/AssignedStores/', [ - 'as' => 'products.assigned.stores', - 'uses' => ProductController::class . '@getAssignedStoresForProduct', - 'middleware' => ['auth', 'module:stores'] -]); - -Route::post('/Admin/Stores/List', [ - 'as' => 'admin.stores.list.data', - 'uses' => StoreController::class . '@listStores_Admin', - 'middleware' => ['auth', 'module:stores'] -]); - -Route::post('/Store/Cooperatives/List', [ - 'as' => 'store.cooperatives.list', - 'uses' => StoreController::class . '@listCooperativesForStore', - 'middleware' => ['auth', 'module:stores'] -]); - -Route::post('/Admin/Store/Delete', [ - 'as' => 'admin.store.delete', - 'uses' => StoreController::class . '@deleteStore_Admin', - 'middleware' => ['auth', 'module:stores'] -]); - -Route::post('/Admin/Store/ToggleStatus', [ - 'as' => 'admin.store.toggle_status', - 'uses' => StoreController::class . '@toggleStoreStatus_Admin', - 'middleware' => ['auth', 'module:stores'] -]); - - -Route::post('/Edit/Store/Product/remove/', [ - 'as' => 'store.product.remove', - 'uses' => StoreController::class . '@removeProductfromStore', - 'middleware' => 'auth' -]); - +// ───────────────────────────────────────────────────────────────────────────── // Announcements -Route::get('/Announcements/Latest', [AnnouncementController::class, 'latest']); -Route::post('/Admin/Announcements/List', [AnnouncementController::class, 'index'], ['middleware' => 'auth']); -Route::post('/Admin/Announcement/Store', [AnnouncementController::class, 'store'], ['middleware' => 'auth']); -Route::post('/Admin/Announcement/Update', [AnnouncementController::class, 'update'], ['middleware' => 'auth']); -Route::post('/Admin/Announcement/Delete', [AnnouncementController::class, 'destroy'], ['middleware' => 'auth']); -Route::post('/Admin/Announcement/ToggleStatus', [AnnouncementController::class, 'toggleStatus'], ['middleware' => 'auth']); +// ───────────────────────────────────────────────────────────────────────────── +Route::get('/announcements/latest', [AnnouncementController::class, 'latest']); +Route::get('/announcements', [AnnouncementController::class, 'index'], ['middleware' => 'auth']); +Route::post('/announcements/create', [AnnouncementController::class, 'store'], ['middleware' => 'auth']); +Route::post('/announcements/update', [AnnouncementController::class, 'update'], ['middleware' => 'auth']); +Route::post('/announcements/delete', [AnnouncementController::class, 'destroy'], ['middleware' => 'auth']); +Route::post('/announcements/toggle', [AnnouncementController::class, 'toggleStatus'], ['middleware' => 'auth']); +// ───────────────────────────────────────────────────────────────────────────── +// System Settings +// ───────────────────────────────────────────────────────────────────────────── -//TODO Add Route for StoreUpdate Edit +Route::get('/admin/settings', [SystemSettingsController::class, 'index'], ['middleware' => 'auth']); +Route::post('/admin/settings/update', [SystemSettingsController::class, 'update'], ['middleware' => 'auth']); +Route::post('/admin/settings/logo/upload', [SystemSettingsController::class, 'uploadLogo'], ['middleware' => 'auth']); +Route::get('/admin/settings/public', [SystemSettingsController::class, 'publicSettings']); -// Global Transactions -Route::post('/admin/transactions/list', [\App\Http\Controllers\GlobalTransactionController::class, 'list'], ['middleware' => ['auth', 'module:transactions']]); -Route::post('/admin/transactions/create', [\App\Http\Controllers\GlobalTransactionController::class, 'store'], ['middleware' => ['auth', 'module:transactions']]); -Route::post('/admin/transactions/types', [\App\Http\Controllers\GlobalTransactionController::class, 'getTypes'], ['middleware' => ['auth', 'module:transactions']]); +// ───────────────────────────────────────────────────────────────────────────── +// Landing Pages +// ───────────────────────────────────────────────────────────────────────────── +Route::get('/admin/landing-pages', [LandingPageController::class, 'index'], ['middleware' => 'auth']); +Route::post('/admin/landing-pages/create', [LandingPageController::class, 'store'], ['middleware' => 'auth']); +Route::post('/admin/landing-pages/update', [LandingPageController::class, 'update'], ['middleware' => 'auth']); +Route::post('/admin/landing-pages/activate', [LandingPageController::class, 'activate'], ['middleware' => 'auth']); +Route::post('/admin/landing-pages/delete', [LandingPageController::class, 'destroy'], ['middleware' => 'auth']); +Route::get('/public/landing-page', [LandingPageController::class, 'active']); -// Property Management -Route::post('/admin/properties/list', [PropertyManagementController::class, 'listProperties'], ['middleware' => ['auth', 'module:properties']]); -Route::post('/admin/properties/referrals', [PropertyManagementController::class, 'listReferrals'], ['middleware' => ['auth', 'module:properties']]); +// ───────────────────────────────────────────────────────────────────────────── +// Admin Console +// ───────────────────────────────────────────────────────────────────────────── -// Accounting & Reports -Route::post('/admin/accounting/tree', [AccountingController::class, 'getAccountsTree'], ['middleware' => ['auth', 'module:accounting']]); -Route::post('/admin/accounting/leaf', [AccountingController::class, 'getLeafAccounts'], ['middleware' => ['auth', 'module:accounting']]); -Route::post('/admin/accounting/daily', [AccountingController::class, 'getDailyTransactions'], ['middleware' => ['auth', 'module:accounting']]); -Route::post('/admin/accounting/daily/save', [AccountingController::class, 'saveDailyTransactions'], ['middleware' => ['auth', 'module:accounting']]); -Route::post('/admin/accounting/transactions', [AccountingController::class, 'listTransactions'], ['middleware' => ['auth', 'module:accounting']]); -Route::post('/admin/accounting/transactions/delete', [AccountingController::class, 'deleteTransaction'], ['middleware' => ['auth', 'module:accounting']]); -Route::post('/admin/accounting/reports', [AccountingController::class, 'listReports'], ['middleware' => ['auth', 'module:accounting']]); -Route::post('/admin/accounting/reports/monthly', [AccountingController::class, 'getMonthlyReport'], ['middleware' => ['auth', 'module:accounting']]); -Route::post('/admin/accounting/accounts/create', [AccountingController::class, 'createAccount'], ['middleware' => ['auth', 'module:accounting']]); -Route::post('/admin/accounting/accounts/update', [AccountingController::class, 'updateAccount'], ['middleware' => ['auth', 'module:accounting']]); -Route::post('/admin/accounting/accounts/archive', [AccountingController::class, 'archiveAccount'], ['middleware' => ['auth', 'module:accounting']]); -Route::post('/admin/accounting/accounts/restore', [AccountingController::class, 'restoreAccount'], ['middleware' => ['auth', 'module:accounting']]); -Route::post('/admin/accounting/accounts/move', [AccountingController::class, 'moveAccount'], ['middleware' => ['auth', 'module:accounting']]); -Route::post('/admin/accounting/theme', [AccountingController::class, 'getThemeInfo'], ['middleware' => ['auth', 'module:accounting']]); -Route::post('/admin/accounting/theme/drift', [AccountingController::class, 'getThemeDrift'], ['middleware' => ['auth', 'module:accounting']]); -Route::post('/admin/accounting/theme/apply', [AccountingController::class, 'applyTheme'], ['middleware' => ['auth', 'module:accounting']]); -Route::post('/admin/accounting/theme/set', [AccountingController::class, 'setTheme'], ['middleware' => ['auth', 'module:accounting']]); +Route::get('/admin/console/stats', [AdminConsoleController::class, 'getSystemStats'], ['middleware' => 'auth']); +Route::get('/admin/console/logs', [AdminConsoleController::class, 'getLogs'], ['middleware' => 'auth']); +Route::get('/admin/console/table-logs', [AdminConsoleController::class, 'getTableLogs'], ['middleware' => 'auth']); +Route::post('/admin/console/query', [AdminConsoleController::class, 'runQuery'], ['middleware' => 'auth']); +Route::post('/admin/console/maintenance', [AdminConsoleController::class, 'setMaintenanceMode'], ['middleware' => 'auth']); +Route::post('/admin/console/message', [AdminConsoleController::class, 'setGlobalMessage'], ['middleware' => 'auth']); +Route::post('/admin/console/cache/clear', [AdminConsoleController::class, 'clearCache'], ['middleware' => 'auth']); +Route::post('/admin/console/backup', [AdminConsoleController::class, 'backupDatabase'], ['middleware' => 'auth']); +Route::get('/admin/console/backups', [AdminConsoleController::class, 'listBackups'], ['middleware' => 'auth']); -// POS Routes -Route::post('/api/pos/access-keys/list', [\App\Http\Controllers\Admin\PosAccessKeyController::class, 'index'], ['middleware' => ['auth', 'module:pos']]); -Route::post('/api/pos/access-keys/create', [\App\Http\Controllers\Admin\PosAccessKeyController::class, 'store'], ['middleware' => ['auth', 'module:pos']]); -Route::post('/api/pos/access-keys/delete', [\App\Http\Controllers\Admin\PosAccessKeyController::class, 'destroy'], ['middleware' => ['auth', 'module:pos']]); -Route::post('/api/pos/access-keys/toggle', [\App\Http\Controllers\Admin\PosAccessKeyController::class, 'toggleStatus'], ['middleware' => ['auth', 'module:pos']]); +// ───────────────────────────────────────────────────────────────────────────── +// API Tokens +// ───────────────────────────────────────────────────────────────────────────── -Route::post('/api/pos/start', [\App\Http\Controllers\Market\PosController::class, 'startSession'], ['middleware' => 'module:pos']); // Allow guest access via key -Route::post('/api/pos/session', [\App\Http\Controllers\Market\PosController::class, 'getSession'], ['middleware' => 'module:pos']); // Public/Guest access -Route::post('/api/pos/add-item', [\App\Http\Controllers\Market\PosController::class, 'addItem'], ['middleware' => 'module:pos']); -Route::post('/api/pos/remove-item', [\App\Http\Controllers\Market\PosController::class, 'removeItem'], ['middleware' => 'module:pos']); -Route::post('/api/pos/complete', [\App\Http\Controllers\Market\PosController::class, 'completeSession'], ['middleware' => 'module:pos']); -Route::post('/api/pos/sync-offline', [\App\Http\Controllers\Market\PosController::class, 'syncOffline'], ['middleware' => 'module:pos']); -Route::post('/api/pos/void', [\App\Http\Controllers\Market\PosController::class, 'voidSession'], ['middleware' => 'module:pos']); -Route::post('/api/pos/stats', [\App\Http\Controllers\Market\PosController::class, 'getTodayStats'], ['middleware' => 'module:pos']); -Route::post('/api/pos/get-customers', [\App\Http\Controllers\Market\PosController::class, 'getCustomers'], ['middleware' => 'module:pos']); -Route::post('/api/pos/sessions/list', [\App\Http\Controllers\Market\PosController::class, 'getPosSessions'], ['middleware' => ['auth', 'module:pos']]); +Route::get('/admin/tokens', [ApiTokenController::class, 'index'], ['middleware' => 'auth']); +Route::get('/admin/tokens/catalog', [ApiTokenController::class, 'catalog'], ['middleware' => 'auth']); +Route::post('/admin/tokens/create', [ApiTokenController::class, 'store'], ['middleware' => 'auth']); +Route::post('/admin/tokens/revoke', [ApiTokenController::class, 'revoke'], ['middleware' => 'auth']); +Route::post('/admin/tokens/delete', [ApiTokenController::class, 'destroy'], ['middleware' => 'auth']); +// ───────────────────────────────────────────────────────────────────────────── +// User Management +// ───────────────────────────────────────────────────────────────────────────── -// Shipment Routes -Route::post('/Shipments/List', [ShipmentController::class, 'listShipments'], ['middleware' => ['auth', 'module:shipments']]); -Route::post('/Shipments/Create', [ShipmentController::class, 'createNewShipment'], ['middleware' => ['auth', 'module:shipments']]); -Route::post('/Shipments/Status/Update', [ShipmentController::class, 'updateShipmentStatus'], ['middleware' => ['auth', 'module:shipments']]); -Route::post('/Couriers/List', [ShipmentController::class, 'listCouriers'], ['middleware' => ['auth', 'module:shipments']]); -Route::post('/Couriers/Create', [ShipmentController::class, 'createCourier'], ['middleware' => ['auth', 'module:shipments']]); +Route::get('/admin/users', [UserController::class, 'index'], ['middleware' => 'auth']); +Route::post('/admin/users/show', [UserController::class, 'show'], ['middleware' => 'auth']); +Route::post('/admin/users/create', [UserController::class, 'store'], ['middleware' => 'auth']); +Route::post('/admin/users/update', [UserController::class, 'update'], ['middleware' => 'auth']); +Route::post('/admin/users/set-active', [UserController::class, 'setActive'], ['middleware' => 'auth']); +Route::post('/admin/users/change-password', [UserController::class, 'changePassword'], ['middleware' => 'auth']); +Route::post('/admin/users/force-logout', [UserController::class, 'forceLogout'], ['middleware' => 'auth']); +Route::get('/admin/users/account-types', [UserController::class, 'accountTypes'], ['middleware' => 'auth']); -// Farmer Management Routes -Route::post('/Farmers/Register', [FarmerController::class, 'registerFarmer'], ['middleware' => ['auth', 'module:farmers']]); -Route::post('/Farmers/List', [FarmerController::class, 'listFarmers'], ['middleware' => ['auth', 'module:farmers']]); -Route::post('/Farmers/Verify', [FarmerController::class, 'verifyFarmer'], ['middleware' => ['auth', 'module:farmers']]); -Route::post('/Organizations/List', [FarmerController::class, 'listOrganizations'], ['middleware' => ['auth', 'module:farmers']]); -Route::post('/Organizations/Create', [FarmerController::class, 'createOrganization'], ['middleware' => ['auth', 'module:farmers']]); +// ───────────────────────────────────────────────────────────────────────────── +// Chapters / Geography Hierarchy +// ───────────────────────────────────────────────────────────────────────────── -// UserInfo Routes -Route::post('/UserInfo/Get', [\App\Http\Controllers\Market\UserInfoController::class, 'getUserInfo'], ['middleware' => 'auth']); -Route::post('/UserInfo/Update', [\App\Http\Controllers\Market\UserInfoController::class, 'updateUserInfo'], ['middleware' => 'auth']); -Route::post('/UserInfo/SearchEmergencyContact', [\App\Http\Controllers\Market\UserInfoController::class, 'searchEmergencyContact'], ['middleware' => 'auth']); +Route::post('/Chapters/Hierarchy', [ChapterController::class, 'hierarchy'], ['middleware' => 'auth']); +Route::post('/Chapters/MapData', [ChapterController::class, 'mapData'], ['middleware' => 'auth']); +Route::post('/Chapters/Members', [ChapterController::class, 'members'], ['middleware' => 'auth']); +Route::post('/Chapters/Member/Assign', [ChapterController::class, 'assignMember'], ['middleware' => 'auth']); +Route::post('/Chapters/Member/Remove', [ChapterController::class, 'removeMember'], ['middleware' => 'auth']); +Route::get('/Chapters/Positions', [ChapterController::class, 'positions'], ['middleware' => 'auth']); +Route::post('/Chapters/Create', [ChapterController::class, 'createChapter'], ['middleware' => 'auth']); -// Cooperative Module Routes -Route::post('/Cooperatives/List', [\App\Http\Controllers\Market\CooperativeController::class, 'listCooperatives'], ['middleware' => ['auth', 'module:cooperatives']]); -Route::post('/Cooperatives/Get', [\App\Http\Controllers\Market\CooperativeController::class, 'getCooperative'], ['middleware' => ['auth', 'module:cooperatives']]); -Route::post('/Cooperatives/Join', [\App\Http\Controllers\Market\CooperativeController::class, 'joinCooperative'], ['middleware' => ['auth', 'module:cooperatives']]); -Route::post('/Cooperatives/Create', [\App\Http\Controllers\Market\CooperativeController::class, 'createCooperative'], ['middleware' => ['auth', 'module:cooperatives']]); -Route::post('/Cooperatives/Member/Add', [\App\Http\Controllers\Market\CooperativeController::class, 'addMember'], ['middleware' => ['auth', 'module:cooperatives']]); -Route::post('/Cooperatives/Member/Register', [\App\Http\Controllers\Market\CooperativeController::class, 'registerMember'], ['middleware' => ['auth', 'module:cooperatives']]); -Route::post('/Cooperatives/Member/Update', [\App\Http\Controllers\Market\CooperativeController::class, 'updateMember'], ['middleware' => ['auth', 'module:cooperatives']]); +// ───────────────────────────────────────────────────────────────────────────── +// QR PH Payment +// ───────────────────────────────────────────────────────────────────────────── -// Governance Module Routes -Route::post('/Governance/Resolutions/List', [\App\Http\Controllers\Market\GovernanceController::class, 'listResolutions'], ['middleware' => ['auth', 'module:cooperatives']]); -Route::post('/Governance/Resolutions/Create', [\App\Http\Controllers\Market\GovernanceController::class, 'createResolution'], ['middleware' => ['auth', 'module:cooperatives']]); -Route::post('/Governance/Resolutions/Vote', [\App\Http\Controllers\Market\GovernanceController::class, 'castVote'], ['middleware' => ['auth', 'module:cooperatives']]); -Route::post('/Governance/Resolutions/Status/Update', [\App\Http\Controllers\Market\GovernanceController::class, 'updateResolutionStatus'], ['middleware' => ['auth', 'module:cooperatives']]); +Route::get('/payment/qrph', [QRPHController::class, 'getQrCode'], ['middleware' => 'auth']); +Route::post('/payment/qrph/set', [QRPHController::class, 'setQrCode'], ['middleware' => 'auth']); +Route::post('/payment/qrph/decode', [QRPHController::class, 'decode'], ['middleware' => 'auth']); +Route::post('/payment/qrph/remove', [QRPHController::class, 'removeQrCode'], ['middleware' => 'auth']); -// Cooperative Document Routes -Route::post('/Cooperatives/Documents/List', [\App\Http\Controllers\Market\CooperativeDocumentController::class, 'listDocuments'], ['middleware' => ['auth', 'module:cooperatives']]); -Route::post('/Cooperatives/Documents/Upload', [\App\Http\Controllers\Market\CooperativeDocumentController::class, 'uploadDocument'], ['middleware' => ['auth', 'module:cooperatives']]); -Route::post('/Cooperatives/Documents/Revise', [\App\Http\Controllers\Market\CooperativeDocumentController::class, 'reviseDocument'], ['middleware' => ['auth', 'module:cooperatives']]); -Route::post('/Cooperatives/Documents/Delete', [\App\Http\Controllers\Market\CooperativeDocumentController::class, 'deleteDocument'], ['middleware' => ['auth', 'module:cooperatives']]); +// ───────────────────────────────────────────────────────────────────────────── +// Document Request Types +// ───────────────────────────────────────────────────────────────────────────── -// Financial & Credit Routes -Route::post('/Financial/Wallet/Get', [\App\Http\Controllers\Market\CreditController::class, 'getWalletData'], ['middleware' => ['auth', 'module:credits']]); -Route::post('/Financial/Credit/TopUp', [\App\Http\Controllers\Market\CreditController::class, 'topUp'], ['middleware' => ['auth', 'module:credits']]); -Route::post('/Financial/Credit/Transfer', [\App\Http\Controllers\Market\CreditController::class, 'transferCredit'], ['middleware' => ['auth', 'module:credits']]); -Route::post('/Financial/Credit/SearchUsers', [\App\Http\Controllers\Market\CreditController::class, 'searchUsers'], ['middleware' => ['auth', 'module:credits']]); -Route::post('/Financial/Qrph/Get', [\App\Http\Controllers\Market\CreditController::class, 'getQrphCode'], ['middleware' => 'auth']); -Route::post('/Financial/Qrph/Set', [\App\Http\Controllers\Market\CreditController::class, 'setQrphCode'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/Financial/Qrph/Decode', [\App\Http\Controllers\Market\CreditController::class, 'decodeQrph'], ['middleware' => ['auth', 'ultimate']]); +Route::get('/request-types', [RequestTypeController::class, 'active']); +Route::get('/admin/request-types', [RequestTypeController::class, 'index'], ['middleware' => 'auth']); +Route::post('/admin/request-types/create', [RequestTypeController::class, 'store'], ['middleware' => 'auth']); +Route::post('/admin/request-types/update', [RequestTypeController::class, 'update'], ['middleware' => 'auth']); +Route::post('/admin/request-types/toggle', [RequestTypeController::class, 'toggleActive'], ['middleware' => 'auth']); -// UserSettings Routes -Route::post('/UserSettings/Get', [\App\Http\Controllers\Market\UserSettingsController::class, 'getSettings'], ['middleware' => 'auth']); -Route::post('/UserSettings/Update', [\App\Http\Controllers\Market\UserSettingsController::class, 'updateSettings'], ['middleware' => 'auth']); +// ───────────────────────────────────────────────────────────────────────────── +// Document Requests +// ───────────────────────────────────────────────────────────────────────────── -// UserAdditionalDetails Routes -Route::post('/UserAdditionalDetails/Get', [\App\Http\Controllers\UserManagement\UserAdditionalDetailsController::class, 'getDetails'], ['middleware' => 'auth']); -Route::post('/UserAdditionalDetails/UpdateCooperatives', [\App\Http\Controllers\UserManagement\UserAdditionalDetailsController::class, 'updateCooperatives'], ['middleware' => 'auth']); -Route::post('/UserAdditionalDetails/SearchByCooperative', [\App\Http\Controllers\UserManagement\UserAdditionalDetailsController::class, 'searchUsersByCooperative'], ['middleware' => 'auth']); -Route::post('/UserAdditionalDetails/GetCooperatives', [\App\Http\Controllers\UserManagement\UserAdditionalDetailsController::class, 'getUserCooperatives'], ['middleware' => 'auth']); +Route::get('/documents/my', [DocumentRequestController::class, 'myRequests'], ['middleware' => 'auth']); +Route::post('/documents/submit', [DocumentRequestController::class, 'store'], ['middleware' => 'auth']); +Route::post('/documents/cancel', [DocumentRequestController::class, 'cancel'], ['middleware' => 'auth']); +Route::get('/admin/documents', [DocumentRequestController::class, 'index'], ['middleware' => 'auth']); +Route::post('/admin/documents/show', [DocumentRequestController::class, 'show'], ['middleware' => 'auth']); +Route::post('/admin/documents/status', [DocumentRequestController::class, 'updateStatus'], ['middleware' => 'auth']); +Route::post('/admin/documents/confirm-payment', [DocumentRequestController::class, 'confirmPayment'], ['middleware' => 'auth']); +Route::post('/admin/documents/mark-ready', [DocumentRequestController::class, 'markReady'], ['middleware' => 'auth']); +Route::post('/admin/documents/mark-claimed', [DocumentRequestController::class, 'markClaimed'], ['middleware' => 'auth']); -// Cart Routes -Route::post('/cart/get', [CartController::class, 'getCart'], ['middleware' => ['auth', 'module:cart']]); -Route::post('/cart/add', [CartController::class, 'addItem'], ['middleware' => ['auth', 'module:cart']]); -Route::post('/cart/update', [CartController::class, 'updateItem'], ['middleware' => ['auth', 'module:cart']]); -Route::post('/cart/remove', [CartController::class, 'removeItem'], ['middleware' => ['auth', 'module:cart']]); -Route::post('/cart/clear', [CartController::class, 'clearCart'], ['middleware' => ['auth', 'module:cart']]); +// ───────────────────────────────────────────────────────────────────────────── +// Residents +// ───────────────────────────────────────────────────────────────────────────── -// Public Cooperative Registration Routes -Route::get('/api/public/cooperative/{hkey}', [\App\Http\Controllers\Market\CooperativeController::class, 'publicGetCooperative']); -Route::post('/api/public/cooperative/register', [\App\Http\Controllers\Market\CooperativeController::class, 'publicRegisterMember']); -Route::post('/api/public/cooperative/complete-membership', [\App\Http\Controllers\Market\CooperativeController::class, 'publicCompleteMembership']); +Route::get('/residents', [ResidentController::class, 'index'], ['middleware' => 'auth']); +Route::post('/residents/show', [ResidentController::class, 'show'], ['middleware' => 'auth']); +Route::post('/residents/create', [ResidentController::class, 'store'], ['middleware' => 'auth']); +Route::post('/residents/update', [ResidentController::class, 'update'], ['middleware' => 'auth']); +Route::post('/residents/set-active', [ResidentController::class, 'setActive'], ['middleware' => 'auth']); +Route::get('/residents/search', [ResidentController::class, 'search'], ['middleware' => 'auth']); +Route::get('/residents/puroks', [ResidentController::class, 'puroks'], ['middleware' => 'auth']); -// Public Chapter Registration Routes -Route::get('/api/public/chapter/{hkey}', [\App\Http\Controllers\Support\ChapterController::class, 'publicGetChapter']); -Route::post('/api/public/chapter/register', [\App\Http\Controllers\Support\ChapterController::class, 'publicRegisterToChapter']); +// ───────────────────────────────────────────────────────────────────────────── +// Households +// ───────────────────────────────────────────────────────────────────────────── -// Public User Self-Registration Routes -Route::post('/api/public/user/register', [\App\Http\Controllers\UserManagement\CreateUserControllerUltimate::class, 'publicRegisterUser']); -Route::post('/api/public/user/check-mobile', [\App\Http\Controllers\UserManagement\CreateUserControllerUltimate::class, 'publicCheckMobileNumber']); +Route::get('/households', [HouseholdController::class, 'index'], ['middleware' => 'auth']); +Route::post('/households/show', [HouseholdController::class, 'show'], ['middleware' => 'auth']); +Route::post('/households/create', [HouseholdController::class, 'store'], ['middleware' => 'auth']); +Route::post('/households/update', [HouseholdController::class, 'update'], ['middleware' => 'auth']); +Route::post('/households/members/add', [HouseholdController::class, 'addMember'], ['middleware' => 'auth']); +Route::post('/households/members/remove', [HouseholdController::class, 'removeMember'], ['middleware' => 'auth']); -// Chapter / Org Hierarchy Routes -Route::post('/Chapters/Hierarchy', [\App\Http\Controllers\Support\ChapterController::class, 'hierarchy'], ['middleware' => 'auth']); -Route::post('/Chapters/MapData', [\App\Http\Controllers\Support\ChapterController::class, 'mapData'], ['middleware' => 'auth']); -Route::post('/Chapters/OrgHierarchy', [\App\Http\Controllers\Support\ChapterController::class, 'orgHierarchy'], ['middleware' => 'auth']); -Route::post('/Chapters/OrgMapData', [\App\Http\Controllers\Support\ChapterController::class, 'orgMapData'], ['middleware' => 'auth']); -Route::post('/Chapters/Members', [\App\Http\Controllers\Support\ChapterController::class, 'members'], ['middleware' => 'auth']); -Route::post('/Chapters/Member/Assign', [\App\Http\Controllers\Support\ChapterController::class, 'assignMember'], ['middleware' => 'auth']); -Route::post('/Chapters/Member/Remove', [\App\Http\Controllers\Support\ChapterController::class, 'removeMember'], ['middleware' => 'auth']); -Route::post('/Chapters/SyncAutoAssignments', [\App\Http\Controllers\Support\ChapterController::class, 'syncAllAutoAssignments'], ['middleware' => ['auth', 'ultimate']]); -Route::get('/Chapters/Positions', [\App\Http\Controllers\Support\ChapterController::class, 'positions'], ['middleware' => 'auth']); -Route::post('/Chapters/OrgChart', [\App\Http\Controllers\Support\ChapterController::class, 'getOrgChart'], ['middleware' => 'auth']); -Route::post('/Chapters/Officer/Assign', [\App\Http\Controllers\Support\ChapterController::class, 'assignOfficer'], ['middleware' => 'auth']); -Route::post('/Chapters/Create', [\App\Http\Controllers\Support\ChapterController::class, 'createChapter'], ['middleware' => 'auth']); -Route::post('/Chapters/Members/Search', [\App\Http\Controllers\Support\ChapterController::class, 'memberSearch'], ['middleware' => 'auth']); -Route::post('/Chapters/Officer/Scope', [\App\Http\Controllers\Support\ChapterController::class, 'getOfficerScope'], ['middleware' => 'auth']); +// ───────────────────────────────────────────────────────────────────────────── +// Blotters +// ───────────────────────────────────────────────────────────────────────────── -// Landing Page Routes -Route::get('/api/public/landing-page', [\App\Http\Controllers\Admin\LandingPageController::class, 'getActiveLandingPage']); -Route::post('/admin/landing-pages/list', [\App\Http\Controllers\Admin\LandingPageController::class, 'index'], ['middleware' => 'auth']); -Route::post('/admin/landing-pages/show', [\App\Http\Controllers\Admin\LandingPageController::class, 'show'], ['middleware' => 'auth']); -Route::post('/admin/landing-pages/store', [\App\Http\Controllers\Admin\LandingPageController::class, 'store'], ['middleware' => 'auth']); -Route::post('/admin/landing-pages/set-active', [\App\Http\Controllers\Admin\LandingPageController::class, 'setActive'], ['middleware' => 'auth']); -Route::post('/admin/landing-pages/deactivate-all', [\App\Http\Controllers\Admin\LandingPageController::class, 'deactivateAll'], ['middleware' => 'auth']); -Route::post('/admin/landing-pages/delete', [\App\Http\Controllers\Admin\LandingPageController::class, 'destroy'], ['middleware' => 'auth']); +Route::get('/blotters', [BlotterController::class, 'index'], ['middleware' => 'auth']); +Route::post('/blotters/show', [BlotterController::class, 'show'], ['middleware' => 'auth']); +Route::post('/blotters/create', [BlotterController::class, 'store'], ['middleware' => 'auth']); +Route::post('/blotters/status', [BlotterController::class, 'updateStatus'], ['middleware' => 'auth']); +Route::post('/blotters/assign-officer', [BlotterController::class, 'assignOfficer'], ['middleware' => 'auth']); +Route::get('/blotters/status-options', [BlotterController::class, 'statusOptions'], ['middleware' => 'auth']); +Route::post('/blotters/hearings', [BlotterHearingController::class, 'index'], ['middleware' => 'auth']); +Route::post('/blotters/hearings/schedule', [BlotterHearingController::class, 'schedule'], ['middleware' => 'auth']); +Route::post('/blotters/hearings/update', [BlotterHearingController::class, 'update'], ['middleware' => 'auth']); -// Ultimate Console Routes -Route::post('/admin/ultimate/system-settings', [\App\Http\Controllers\Admin\SystemSettingsController::class, 'index'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/admin/ultimate/system-settings/update', [\App\Http\Controllers\Admin\SystemSettingsController::class, 'update'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/admin/ultimate/system-settings/logo', [\App\Http\Controllers\Admin\SystemSettingsController::class, 'uploadLogo'], ['middleware' => ['auth', 'ultimate']]); -Route::get('/admin/ultimate/system-settings/organizations', [\App\Http\Controllers\Admin\SystemSettingsController::class, 'listOrganizations'], ['middleware' => ['auth', 'ultimate']]); -Route::get('/admin/ultimate/modules', [\App\Http\Controllers\Admin\SystemSettingsController::class, 'getModules'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/admin/ultimate/modules/update', [\App\Http\Controllers\Admin\SystemSettingsController::class, 'updateModules'], ['middleware' => ['auth', 'ultimate']]); +// ───────────────────────────────────────────────────────────────────────────── +// Projects +// ───────────────────────────────────────────────────────────────────────────── -// API tokens — issued from Ultimate Console -Route::get('/admin/ultimate/api-tokens/catalog', [\App\Http\Controllers\Admin\ApiTokenController::class, 'catalog'], ['middleware' => ['auth', 'ultimate']]); -Route::get('/admin/ultimate/api-tokens', [\App\Http\Controllers\Admin\ApiTokenController::class, 'index'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/admin/ultimate/api-tokens', [\App\Http\Controllers\Admin\ApiTokenController::class, 'store'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/admin/ultimate/api-tokens/{id}/revoke', [\App\Http\Controllers\Admin\ApiTokenController::class, 'revoke'], ['middleware' => ['auth', 'ultimate']]); -Route::delete('/admin/ultimate/api-tokens/{id}', [\App\Http\Controllers\Admin\ApiTokenController::class, 'destroy'], ['middleware' => ['auth', 'ultimate']]); -Route::get('/api/public/system-settings', [\App\Http\Controllers\Admin\SystemSettingsController::class, 'getPublicSettings']); -Route::get('/api/public/global-message', function () { - $message = \Hypervel\Support\Facades\Redis::get('system:global_message'); - if ($message) { - return response()->json(['success' => true, 'data' => json_decode($message, true)]); - } - return response()->json(['success' => true, 'data' => null]); -}); +Route::get('/projects', [ProjectController::class, 'index'], ['middleware' => 'auth']); +Route::post('/projects/show', [ProjectController::class, 'show'], ['middleware' => 'auth']); +Route::post('/projects/create', [ProjectController::class, 'store'], ['middleware' => 'auth']); +Route::post('/projects/update', [ProjectController::class, 'update'], ['middleware' => 'auth']); +Route::post('/projects/status', [ProjectController::class, 'updateStatus'], ['middleware' => 'auth']); +Route::get('/projects/summary', [ProjectController::class, 'summary'], ['middleware' => 'auth']); -Route::post('/admin/ultimate/stats', [\App\Http\Controllers\Market\UltimateController::class, 'getSystemStats'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/admin/ultimate/query', [\App\Http\Controllers\Market\UltimateController::class, 'runQuery'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/admin/ultimate/maintenance/toggle', [\App\Http\Controllers\Market\UltimateController::class, 'toggleMaintenance'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/admin/ultimate/global-message', [\App\Http\Controllers\Market\UltimateController::class, 'sendGlobalMessage'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/admin/ultimate/flush', [\App\Http\Controllers\Market\UltimateController::class, 'flushData'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/admin/ultimate/test-notification', [\App\Http\Controllers\Market\UltimateController::class, 'testNotification'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/admin/ultimate/batch', [\App\Http\Controllers\Market\UltimateController::class, 'batchManage'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/admin/ultimate/command', [\App\Http\Controllers\Market\UltimateController::class, 'runCommand'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/admin/ultimate/migrate', [\App\Http\Controllers\Market\UltimateController::class, 'runMigrate'], ['middleware' => ['auth', 'ultimate']]); -Route::get('/admin/ultimate/backup/download', [\App\Http\Controllers\Market\UltimateController::class, 'downloadBackup'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/admin/ultimate/logs', [\App\Http\Controllers\Market\UltimateController::class, 'getSystemLogs'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/admin/ultimate/backups/list', [\App\Http\Controllers\Market\UltimateController::class, 'getBackups'], ['middleware' => ['auth', 'ultimate']]); -Route::get('/admin/ultimate/backup/download/hash', [\App\Http\Controllers\Market\UltimateController::class, 'downloadBackupByHash'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/admin/ultimate/backup/rename', [\App\Http\Controllers\Market\UltimateController::class, 'renameBackup'], ['middleware' => ['auth', 'ultimate']]); -Route::post('/admin/ultimate/backup/delete', [\App\Http\Controllers\Market\UltimateController::class, 'deleteBackup'], ['middleware' => ['auth', 'ultimate']]); +// ───────────────────────────────────────────────────────────────────────────── +// Budget & Finance +// ───────────────────────────────────────────────────────────────────────────── - -// Batch Creation Routes -Route::post('/admin/batch/products', [\App\Http\Controllers\Market\BatchController::class, 'batchCreateProducts'], ['middleware' => ['auth', 'module:batch']]); -Route::get('/admin/batch/products/template', [\App\Http\Controllers\Market\BatchController::class, 'downloadProductTemplate'], ['middleware' => ['auth', 'module:batch']]); -Route::post('/admin/batch/stores', [\App\Http\Controllers\Market\BatchController::class, 'batchCreateStores'], ['middleware' => ['auth', 'module:batch']]); -Route::post('/admin/batch/users', [\App\Http\Controllers\Market\BatchController::class, 'batchCreateUsers'], ['middleware' => ['auth', 'module:batch']]); -Route::post('/admin/batch/cooperatives', [\App\Http\Controllers\Market\BatchController::class, 'batchCreateCooperatives'], ['middleware' => ['auth', 'module:batch']]); -Route::post('/admin/batch/cooperative-members', [\App\Http\Controllers\Market\BatchController::class, 'batchCreateCooperativeMembers'], ['middleware' => ['auth', 'module:batch', 'module:cooperatives']]); - - -// Universal SPA Route - Handles all other paths by mapping them to Vue components -Route::get('/{path}', function ($path = '/') { - return VueRouteMap::handleSpa($path); -}, [ - 'where' => ['path' => '.*'], - 'middleware' => 'web' -]); - - -// Route::get('/data/hello/', function (\Hypervel\Http\Request $request) { -// $file = public_path('/plugins/bootstrap/js/bootstrap.bundle.min.js'); - -// if ($request->has('nocache')) { -// return response()->file($file); -// } - -// $cacheKey = 'file_cache:bootstrap_bundle_js'; - -// $content = Cache::remember($cacheKey, 3600, function () use ($file) { -// return File::get($file); -// }); - -// return Response::make($content, 200, [ -// 'Content-Type' => 'application/javascript', -// 'X-Cache' => Cache::has($cacheKey) ? 'HIT' : 'MISS' -// ]); -// }); - - -// Route::get('logs/now', function(){ -// $dd = Storage::get('logs/view_errors.log'); - -// return Response::json(Json::decode($dd),200); -// }); \ No newline at end of file +Route::get('/budget', [BudgetController::class, 'index'], ['middleware' => 'auth']); +Route::get('/budget/summary', [BudgetController::class, 'summary'], ['middleware' => 'auth']); +Route::get('/budget/fiscal-years', [BudgetController::class, 'fiscalYears'], ['middleware' => 'auth']); +Route::post('/budget/create', [BudgetController::class, 'store'], ['middleware' => 'auth']); +Route::post('/budget/update', [BudgetController::class, 'update'], ['middleware' => 'auth']); +Route::post('/budget/delete', [BudgetController::class, 'destroy'], ['middleware' => 'auth']);