--- task: Fix accounting theme switch+apply — switching theme should auto-seed accounts, apply should not silently fail cycles: 5 context: true private: false started: 2026-05-29T00:00:00Z finished: 2026-05-29T00:00:30Z --- ## files - `resources/js/Pages/ManageAccounts.vue` [lines 457-491] — switchTheme() and reapplyTheme() JS functions - `app/Http/Controllers/Accounting/AccountingController.php` [lines 677-715] — setTheme() and applyTheme() controllers; also getAccountsTree() lines 78-109 has a scoping bug - `app/Support/AccountingTheme.php` [lines 201-276] — static apply() / applyNode() logic ## steps ### Bug 1 — "Switch" does not seed accounts (primary UX failure) 1. In `ManageAccounts.vue` `switchTheme()` (line ~457), after `POST /admin/accounting/theme/set` succeeds, immediately call `POST /admin/accounting/theme/apply` (reuse `reapplyTheme()` logic inline or call it). Concrete change: after `await loadThemes(); await fetchAll();` inside the `if (res.data?.success)` block, also call `await reapplyTheme()` — OR refactor: make `switchTheme()` call a combined backend endpoint. Simplest safe fix: after confirming `setTheme` success, call `reapplyTheme()` directly: ```js if (res.data?.success) { await setThemeKey(selectThemeKey.value); // already done await reapplyTheme(); // NEW: seed accounts for the newly-active theme } ``` Since `reapplyTheme()` reads the current theme from the backend (not a parameter), and `setTheme` already saved it, the order is correct. ### Bug 2 — `getAccountsTree()` discards scope on grandchildren (silent data loss) 2. In `AccountingController.php` `getAccountsTree()` (lines 100-102), the innermost `with()` closure does: ```php $this->scopeAccounts($q2, $storeIds, $isBig3); // return value DISCARDED ``` Fix: capture the return value: ```php $q2 = $this->scopeAccounts($q2, $storeIds, $isBig3); ``` Line 102 — change `$this->scopeAccounts($q2, $storeIds, $isBig3);` → `$q2 = $this->scopeAccounts($q2, $storeIds, $isBig3);` ### Bug 3 — `applyTheme()` has no error handling; DB failures are silent 500s 3. In `AccountingController.php` `applyTheme()` (line ~696), wrap the `AccountingTheme::apply()` call in try/catch: ```php try { $stats = \App\Support\AccountingTheme::apply(); } catch (\Throwable $e) { return response()->json([ 'success' => false, 'message' => 'Failed to apply theme: ' . $e->getMessage(), ], 422); } ``` ### Bug 4 — switchTheme UX: don't allow re-clicking Switch on already-active theme after apply 4. After `reapplyTheme()` completes inside `switchTheme()`, call `loadThemes()` once more so `themeInfo.current` is refreshed and the Switch button re-disables correctly. (If `reapplyTheme()` already calls `loadThemes()`, this is automatic — just verify no double-call confusion.) ## context ### ManageAccounts.vue switchTheme() (lines 457-473): ```js async function switchTheme() { if (!selectThemeKey.value || selectThemeKey.value === themeInfo.value?.current) return; switchingTheme.value = true; try { const res = await axios.post('/admin/accounting/theme/set', { key: selectThemeKey.value }); if (res.data?.success) { await loadThemes(); await fetchAll(); // BUG: no apply call here — accounts never seeded } else { showNotice(res.data?.message || 'Could not switch theme.', { variant: 'danger' }); } } catch (e) { showNotice(e.response?.data?.message || 'Could not switch theme.', { variant: 'danger' }); } finally { switchingTheme.value = false; } } ``` ### ManageAccounts.vue reapplyTheme() (lines 475-491): ```js async function reapplyTheme() { applyingTheme.value = true; try { const res = await axios.post('/admin/accounting/theme/apply', {}); if (res.data?.success) { showNotice(res.data.message || 'Theme applied.', { variant: 'success', title: 'Theme Applied' }); await loadThemes(); await fetchAll(); } else { showNotice(res.data?.message || 'Apply failed.', { variant: 'danger' }); } } catch (e) { showNotice(e.response?.data?.message || 'Apply failed.', { variant: 'danger' }); } finally { applyingTheme.value = false; } } ``` ### AccountingController.php getAccountsTree() children scope (lines 96-105): ```php ->with(['children' => function ($q) use ($storeIds, $isBig3) { $q = $q->where('is_active', true); $q = $this->scopeAccounts($q, $storeIds, $isBig3); // ✓ captured $q->with(['children' => function ($q2) use ($storeIds, $isBig3) { $q2 = $q2->where('is_active', true); $this->scopeAccounts($q2, $storeIds, $isBig3); // ✗ BUG: return discarded }]); }]) ``` ### AccountingController.php applyTheme() (lines 696-715): ```php public function applyTheme(Request $request) { if (!UserPermissions::isActionPermitted(...)) { return ResponseHelper::returnUnauthorized(); } $stats = \App\Support\AccountingTheme::apply(); // no try-catch return response()->json(['success' => true, 'message' => ..., 'data' => $stats]); } ``` ### AccountingTheme::apply() tree source: - Reads `Config::get('accounting.themes', [])` from `config/accounting/themes.php` - `banana_trading` key has `tree` with 4 root nodes (Sales, Supplier Purchases, Delivery & Logistics, Operating Expenses) + leaf children - `blank` key has `'tree' => []` — if `current()` falls back to `blank`, apply creates 0 accounts ### Scope for Big3 vs store-level: - `scopeAccounts($query, $storeIds, $isBig3)`: Big3 → `whereNull('store_id')`, store-level → `whereIn('store_id', $storeIds)` - `applyNode()` creates accounts with `store_id` absent (NULL) → correct for Big3 global chart - Root query: `whereNull('parent_id') AND is_active = 1 AND store_id IS NULL` → should match theme-applied accounts ## notes - dictionary: ai-docs/dictionary.md - linters: none detected - constraints: Do NOT delete existing accounts; apply is idempotent/additive. Do NOT change the `setTheme` backend — it is correct as-is (just saves key). The frontend fix in `switchTheme()` is the primary fix. Wrap `reapplyTheme()` call inside `switchTheme()` with `switchingTheme.value = true` still active so the button stays disabled during the full switch+apply operation.