Files
BarangaySystem/.claude/plans/3c56b393bd94fe01eed118b965a18df0-complete.md
2026-06-06 18:43:00 +08:00

6.2 KiB

task, cycles, context, private, started, finished
task cycles context private started finished
Fix accounting theme switch+apply — switching theme should auto-seed accounts, apply should not silently fail 5 true false 2026-05-29T00:00:00Z 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:

    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)

  1. In AccountingController.php getAccountsTree() (lines 100-102), the innermost with() closure does:
    $this->scopeAccounts($q2, $storeIds, $isBig3);  // return value DISCARDED
    
    Fix: capture the return value:
    $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

  1. In AccountingController.php applyTheme() (line ~696), wrap the AccountingTheme::apply() call in try/catch:
    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

  1. 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):

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):

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):

->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):

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.