Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions ledger_lab/api/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,27 @@
VALID_SCOPES = frozenset({"fy", "all"})


def _first_accessible_company() -> str | None:
"""Oldest company the current user can read, or None if there are none."""
companies = frappe.get_list("Company", pluck="name", order_by="creation asc", limit=1)
return companies[0] if companies else None


def _resolve_company(company: str | None) -> str:
company = company.strip() if company else None
if not company:
company = frappe.defaults.get_user_default("Company") or frappe.db.get_single_value(
"Global Defaults", "default_company"
)
# A missing or stale default (e.g. a deleted/renamed company, or a leftover
# user default pointing at a company that no longer exists) shouldn't blank
# out the dashboard. Fall back to the first company the user can access so
# the page always opens with data. The Company picker only ever submits real
# companies, so this lenient fallback never masks a genuine bad selection.
if not company or not frappe.db.exists("Company", company):
company = _first_accessible_company()
if not company:
frappe.throw(_("No company found. Please set a default company."))

if not frappe.db.exists("Company", company):
frappe.throw(_("Company {0} does not exist.").format(company))
frappe.throw(_("No company found. Please create a company first."))

frappe.has_permission("Company", ptype="read", doc=company, throw=True)
return company
Expand Down
17 changes: 17 additions & 0 deletions ledger_lab/ledger_lab/page/ledger_lab/ledger_lab.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,8 @@ class LedgerLab {
if (this.company) this.company_field.set_value(this.company);

// Scope tabs: This Fiscal Year / All Time.
// (sync_company_field below keeps the picker honest once the server
// echoes the authoritatively-resolved company.)
this.page.main.find(".ll-scope-tab").on("click", (e) => {
const scope = e.currentTarget.getAttribute("data-scope");
if (scope === this.scope) return;
Expand Down Expand Up @@ -558,6 +560,16 @@ class LedgerLab {
this.load_feed();
}

// Point the picker at the server-resolved company without re-triggering a
// reload (the change handler no-ops when the value already matches
// this.company, which refresh() sets just before calling this).
sync_company_field() {
if (!this.company_field || !this.company) return;
if (this.company_field.get_value() !== this.company) {
this.company_field.set_value(this.company);
}
}

bind_realtime() {
frappe.realtime.off("ledger_lab_gl_posted");
frappe.realtime.on("ledger_lab_gl_posted", (data) => {
Expand Down Expand Up @@ -592,6 +604,11 @@ class LedgerLab {
callback: (r) => {
if (!r.message) return;
this.company = r.message.company;
// Reflect the server-resolved company in the picker. Handles the
// case where the client-side default was empty or stale (pointing
// at a company that no longer exists): the box loads data for the
// real fallback company, so the picker should show it too.
this.sync_company_field();
this.currency = r.message.currency;
this.date_range = r.message.date_range || { start: null, end: null };
const b = r.message.boxes || {};
Expand Down
Loading