taxes workspace: add Taxes Az subsystem via after_migrate setup
Creates a custom /desk subsystem "Taxes Az" listing user-facing doctypes from the taxes_az module in three cards (Declarations & Returns, Setup & Reference, Period Closing & Tools). The doctype list is a STATIC snapshot baked into taxes_workspace.py — adding a new DocType to taxes_az will not automatically put it into the workspace; edit the tuples and re-run setup. - Uses "Taxes Az" name (not "Taxes") to avoid collision with erpnext's built-in Workspace Sidebar "Taxes", which otherwise hijacks routing to /app/sales-taxes-and-charges-template. - Creates a matching Workspace Sidebar whose first Link is link_type= "Workspace" so Frappe's desktop.js get_route() generates a clean /app/taxes-az route and the click stays in-tab (standard behaviour). - Desktop Icon with idx=999 pins it to the end of the /desk grid. - Cleanup of the obsolete "Taxes" doc we created earlier is idempotent and scoped to jey_theme-owned records (never touches erpnext's). Registered via after_migrate hook so every `bench migrate` re-asserts the configuration. setup() is idempotent. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6c59231f43
commit
084c92466a
|
|
@ -28,6 +28,9 @@ has_permission = {
|
||||||
"*": "jey_theme.access_control.has_permission",
|
"*": "jey_theme.access_control.has_permission",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Idempotent setup of the custom Taxes workspace (see setup/taxes_workspace.py).
|
||||||
|
after_migrate = ["jey_theme.setup.taxes_workspace.setup"]
|
||||||
|
|
||||||
# override_doctype_class = {
|
# override_doctype_class = {
|
||||||
# "Report": "jey_theme.custom_report.CustomReport"
|
# "Report": "jey_theme.custom_report.CustomReport"
|
||||||
# }
|
# }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,281 @@
|
||||||
|
"""Idempotent setup of a 'Taxes Az' workspace and /desk icon.
|
||||||
|
|
||||||
|
The doctype list is a STATIC snapshot baked into this file. Adding a new
|
||||||
|
DocType to the taxes_az app will NOT automatically put it into the workspace
|
||||||
|
— to include one, append it to the appropriate tuple below and re-run
|
||||||
|
`bench migrate` (or `bench execute jey_theme.setup.taxes_workspace.setup`).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
|
WORKSPACE_NAME = "Taxes Az"
|
||||||
|
WORKSPACE_MODULE = "Taxes Az"
|
||||||
|
WORKSPACE_ICON = "landmark"
|
||||||
|
DESKTOP_ICON_LABEL = "Taxes Az"
|
||||||
|
# High idx pushes the icon to the very end of the /desk grid.
|
||||||
|
DESKTOP_ICON_IDX = 999
|
||||||
|
|
||||||
|
CARD_DECLARATIONS = "Declarations & Returns"
|
||||||
|
CARD_SETUP = "Setup & Reference"
|
||||||
|
CARD_TOOLS = "Period Closing & Tools"
|
||||||
|
|
||||||
|
# Obsolete docs we created under the old "Taxes" name; cleaned up on setup
|
||||||
|
# (only if they're owned by jey_theme — we never touch erpnext's own docs).
|
||||||
|
OBSOLETE_NAMES = ("Taxes",)
|
||||||
|
|
||||||
|
# --- Static doctype snapshot (taxes_az as of 2026-04-14) -----------------
|
||||||
|
# To add/remove items, edit these tuples by hand.
|
||||||
|
|
||||||
|
DECLARATIONS_DOCTYPES: tuple[str, ...] = (
|
||||||
|
"Building construction and simplified tax declaration",
|
||||||
|
"Certificate on non-emergence of tax liability",
|
||||||
|
"Declaration of mining tax",
|
||||||
|
"Declaration of simplified tax",
|
||||||
|
"Declaration of simplified tax on cash withdrawals",
|
||||||
|
"Declaration of value added tax",
|
||||||
|
"Declaration of withholding tax on winnings",
|
||||||
|
"Excise declaration",
|
||||||
|
"Export declaration",
|
||||||
|
"Housing construction simplified tax declaration",
|
||||||
|
"Income tax return",
|
||||||
|
"Income tax return of a private notary",
|
||||||
|
"Land tax declaration",
|
||||||
|
"Property tax return",
|
||||||
|
"Quarterly report on unemployment insurance",
|
||||||
|
"Real estate simplified tax declaration",
|
||||||
|
"REFERENCE on controlled transactions",
|
||||||
|
"Report on collection of state duty",
|
||||||
|
"Road tax 2",
|
||||||
|
"Single declaration related to salaried and non-salaried work",
|
||||||
|
"Tax return withheld at source of payment",
|
||||||
|
"VAT allocation",
|
||||||
|
"VAT calculated on the amount paid to a non-resident",
|
||||||
|
"Withholding tax return of foreign subcontractors",
|
||||||
|
)
|
||||||
|
|
||||||
|
SETUP_DOCTYPES: tuple[str, ...] = (
|
||||||
|
"Agricultural Purpose Type",
|
||||||
|
"Business Classification",
|
||||||
|
"Classification code",
|
||||||
|
"EQM Codes",
|
||||||
|
"Main type of activity",
|
||||||
|
"Profit tax with special tax regime",
|
||||||
|
"Tax Article",
|
||||||
|
"Tax Article Items Report",
|
||||||
|
)
|
||||||
|
|
||||||
|
TOOLS_DOCTYPES: tuple[str, ...] = (
|
||||||
|
"Tax Period Closing Settings",
|
||||||
|
"Tax Period Closing Wizard",
|
||||||
|
"Tax Year Closing Wizard",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _all_doctypes() -> list[str]:
|
||||||
|
return list(DECLARATIONS_DOCTYPES) + list(SETUP_DOCTYPES) + list(TOOLS_DOCTYPES)
|
||||||
|
|
||||||
|
|
||||||
|
def _link_row(label: str) -> dict:
|
||||||
|
return {
|
||||||
|
"dependencies": "",
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": label,
|
||||||
|
"link_count": 0,
|
||||||
|
"link_to": label,
|
||||||
|
"link_type": "DocType",
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Link",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _card_break(label: str, count: int) -> dict:
|
||||||
|
return {
|
||||||
|
"hidden": 0,
|
||||||
|
"is_query_report": 0,
|
||||||
|
"label": label,
|
||||||
|
"link_count": count,
|
||||||
|
"onboard": 0,
|
||||||
|
"type": "Card Break",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _build_links() -> list[dict]:
|
||||||
|
links: list[dict] = []
|
||||||
|
for card_label, items in (
|
||||||
|
(CARD_DECLARATIONS, DECLARATIONS_DOCTYPES),
|
||||||
|
(CARD_SETUP, SETUP_DOCTYPES),
|
||||||
|
(CARD_TOOLS, TOOLS_DOCTYPES),
|
||||||
|
):
|
||||||
|
links.append(_card_break(card_label, len(items)))
|
||||||
|
for dt in items:
|
||||||
|
links.append(_link_row(dt))
|
||||||
|
return links
|
||||||
|
|
||||||
|
|
||||||
|
def _build_content() -> str:
|
||||||
|
return json.dumps([
|
||||||
|
{"id": "jeyTxH1", "type": "header",
|
||||||
|
"data": {"text": "<span class=\"h4\"><b>Taxes (Azerbaijan)</b></span>", "col": 12}},
|
||||||
|
{"id": "jeyTxC1", "type": "card",
|
||||||
|
"data": {"card_name": CARD_DECLARATIONS, "col": 12}},
|
||||||
|
{"id": "jeyTxC2", "type": "card",
|
||||||
|
"data": {"card_name": CARD_SETUP, "col": 6}},
|
||||||
|
{"id": "jeyTxC3", "type": "card",
|
||||||
|
"data": {"card_name": CARD_TOOLS, "col": 6}},
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def _cleanup_obsolete() -> list[str]:
|
||||||
|
msgs = []
|
||||||
|
for old in OBSOLETE_NAMES:
|
||||||
|
if frappe.db.exists("Workspace", old):
|
||||||
|
if frappe.db.get_value("Workspace", old, "app") == "jey_theme":
|
||||||
|
frappe.delete_doc("Workspace", old, ignore_permissions=True, force=1)
|
||||||
|
msgs.append(f"deleted obsolete Workspace {old!r}")
|
||||||
|
di_name = frappe.db.get_value("Desktop Icon", {"label": old, "app": "jey_theme"})
|
||||||
|
if di_name:
|
||||||
|
frappe.delete_doc("Desktop Icon", di_name, ignore_permissions=True, force=1)
|
||||||
|
msgs.append(f"deleted obsolete Desktop Icon {old!r}")
|
||||||
|
return msgs
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_workspace() -> str:
|
||||||
|
links = _build_links()
|
||||||
|
content = _build_content()
|
||||||
|
|
||||||
|
if frappe.db.exists("Workspace", WORKSPACE_NAME):
|
||||||
|
ws = frappe.get_doc("Workspace", WORKSPACE_NAME)
|
||||||
|
ws.module = WORKSPACE_MODULE
|
||||||
|
ws.icon = WORKSPACE_ICON
|
||||||
|
ws.public = 1
|
||||||
|
ws.type = "Workspace"
|
||||||
|
ws.content = content
|
||||||
|
ws.links = []
|
||||||
|
for link in links:
|
||||||
|
ws.append("links", link)
|
||||||
|
ws.save(ignore_permissions=True)
|
||||||
|
return (f"updated {WORKSPACE_NAME} "
|
||||||
|
f"(decl={len(DECLARATIONS_DOCTYPES)} "
|
||||||
|
f"setup={len(SETUP_DOCTYPES)} "
|
||||||
|
f"tools={len(TOOLS_DOCTYPES)})")
|
||||||
|
|
||||||
|
ws = frappe.get_doc({
|
||||||
|
"doctype": "Workspace",
|
||||||
|
"name": WORKSPACE_NAME,
|
||||||
|
"label": WORKSPACE_NAME,
|
||||||
|
"title": WORKSPACE_NAME,
|
||||||
|
"module": WORKSPACE_MODULE,
|
||||||
|
"app": "jey_theme",
|
||||||
|
"type": "Workspace",
|
||||||
|
"icon": WORKSPACE_ICON,
|
||||||
|
"public": 1,
|
||||||
|
"is_hidden": 0,
|
||||||
|
"content": content,
|
||||||
|
"links": links,
|
||||||
|
})
|
||||||
|
ws.insert(ignore_permissions=True)
|
||||||
|
return (f"created {WORKSPACE_NAME} "
|
||||||
|
f"(decl={len(DECLARATIONS_DOCTYPES)} "
|
||||||
|
f"setup={len(SETUP_DOCTYPES)} "
|
||||||
|
f"tools={len(TOOLS_DOCTYPES)})")
|
||||||
|
|
||||||
|
|
||||||
|
def _build_sidebar_items() -> list[dict]:
|
||||||
|
"""First item is a Workspace link — get_route() in desktop.js picks the
|
||||||
|
first Link and, because its link_type is 'Workspace', routes to the
|
||||||
|
workspace page. The rest are the doctype links so the left sidebar in
|
||||||
|
/app/taxes-az shows every Taxes Az doctype."""
|
||||||
|
items: list[dict] = [{
|
||||||
|
"type": "Link",
|
||||||
|
"label": WORKSPACE_NAME,
|
||||||
|
"link_type": "Workspace",
|
||||||
|
"link_to": WORKSPACE_NAME,
|
||||||
|
"icon": WORKSPACE_ICON,
|
||||||
|
}]
|
||||||
|
for dt in _all_doctypes():
|
||||||
|
items.append({
|
||||||
|
"type": "Link",
|
||||||
|
"label": dt,
|
||||||
|
"link_type": "DocType",
|
||||||
|
"link_to": dt,
|
||||||
|
})
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_workspace_sidebar() -> str:
|
||||||
|
"""Manual Workspace Sidebar override so Desktop Icon's link_type
|
||||||
|
'Workspace Sidebar' validates and get_route() routes to our workspace."""
|
||||||
|
item_rows = _build_sidebar_items()
|
||||||
|
|
||||||
|
if frappe.db.exists("Workspace Sidebar", WORKSPACE_NAME):
|
||||||
|
sb = frappe.get_doc("Workspace Sidebar", WORKSPACE_NAME)
|
||||||
|
sb.title = WORKSPACE_NAME
|
||||||
|
sb.module = WORKSPACE_MODULE
|
||||||
|
sb.header_icon = WORKSPACE_ICON
|
||||||
|
sb.app = "jey_theme"
|
||||||
|
sb.standard = 1
|
||||||
|
sb.items = []
|
||||||
|
for row in item_rows:
|
||||||
|
sb.append("items", row)
|
||||||
|
sb.save(ignore_permissions=True)
|
||||||
|
return f"updated Workspace Sidebar {WORKSPACE_NAME!r}"
|
||||||
|
|
||||||
|
sb = frappe.get_doc({
|
||||||
|
"doctype": "Workspace Sidebar",
|
||||||
|
"name": WORKSPACE_NAME,
|
||||||
|
"title": WORKSPACE_NAME,
|
||||||
|
"module": WORKSPACE_MODULE,
|
||||||
|
"header_icon": WORKSPACE_ICON,
|
||||||
|
"app": "jey_theme",
|
||||||
|
"standard": 1,
|
||||||
|
"items": item_rows,
|
||||||
|
})
|
||||||
|
sb.insert(ignore_permissions=True)
|
||||||
|
return f"created Workspace Sidebar {WORKSPACE_NAME!r}"
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_desktop_icon() -> str:
|
||||||
|
existing = frappe.db.exists("Desktop Icon", {"label": DESKTOP_ICON_LABEL})
|
||||||
|
if existing:
|
||||||
|
icon = frappe.get_doc("Desktop Icon", existing)
|
||||||
|
icon.icon = WORKSPACE_ICON
|
||||||
|
icon.icon_type = "Link"
|
||||||
|
icon.link_type = "Workspace Sidebar"
|
||||||
|
icon.link_to = WORKSPACE_NAME
|
||||||
|
icon.link = None
|
||||||
|
icon.app = "jey_theme"
|
||||||
|
icon.hidden = 0
|
||||||
|
icon.idx = DESKTOP_ICON_IDX
|
||||||
|
icon.save(ignore_permissions=True)
|
||||||
|
return f"updated Desktop Icon {DESKTOP_ICON_LABEL!r}"
|
||||||
|
|
||||||
|
icon = frappe.get_doc({
|
||||||
|
"doctype": "Desktop Icon",
|
||||||
|
"label": DESKTOP_ICON_LABEL,
|
||||||
|
"icon": WORKSPACE_ICON,
|
||||||
|
"icon_type": "Link",
|
||||||
|
"link_type": "Workspace Sidebar",
|
||||||
|
"link_to": WORKSPACE_NAME,
|
||||||
|
"standard": 1,
|
||||||
|
"app": "jey_theme",
|
||||||
|
"hidden": 0,
|
||||||
|
"idx": DESKTOP_ICON_IDX,
|
||||||
|
})
|
||||||
|
icon.insert(ignore_permissions=True)
|
||||||
|
return f"created Desktop Icon {DESKTOP_ICON_LABEL!r}"
|
||||||
|
|
||||||
|
|
||||||
|
def setup():
|
||||||
|
"""Entry point — safe to call repeatedly (e.g. from after_migrate)."""
|
||||||
|
results: list[str] = []
|
||||||
|
results.extend(_cleanup_obsolete())
|
||||||
|
results.append(ensure_workspace())
|
||||||
|
results.append(ensure_workspace_sidebar())
|
||||||
|
results.append(ensure_desktop_icon())
|
||||||
|
frappe.db.commit()
|
||||||
|
for r in results:
|
||||||
|
if r:
|
||||||
|
print(r)
|
||||||
Loading…
Reference in New Issue