lambda-erp 0.4.0__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/PKG-INFO +1 -1
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/chat.py +58 -4
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/main.py +3 -2
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/routers/masters.py +4 -17
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/services.py +45 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/frontend/src/api/client.ts +1 -0
- lambda_erp-0.5.0/lambda_erp/__init__.py +33 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/pyproject.toml +1 -1
- lambda_erp-0.4.0/lambda_erp/__init__.py +0 -3
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/.gitignore +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/LICENSE +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/README.md +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/__init__.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/attachments.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/auth.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/bootstrap.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/demo_limits.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/deps.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/errors.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/oauth.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/pdf.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/providers.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/remarks_md.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/routers/__init__.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/routers/accounting.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/routers/admin.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/routers/analytics.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/routers/bank_reconciliation.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/routers/chat_api.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/routers/documents.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/routers/proposals.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/routers/reports.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/routers/setup.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/templates/document.html +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/api/templates/proposal.html +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/docs/agents/README.md +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/frontend/LICENSE +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/frontend/README.md +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/__init__.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/bank_transaction.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/budget.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/chart_of_accounts.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/general_ledger.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/journal_entry.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/payment_entry.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/pos_invoice.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/purchase_invoice.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/revaluation.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/sales_invoice.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/setup/__init__.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/setup/engine.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/setup/pack.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/setup/packs/__init__.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/setup/packs/ch.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/setup/packs/de_common.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/setup/packs/de_skr03.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/setup/packs/de_skr04.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/setup/packs/generic.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/setup/profiles.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/setup/spine.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/accounting/subscription.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/buying/__init__.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/buying/purchase_order.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/controllers/__init__.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/controllers/currency.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/controllers/defaults.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/controllers/pricing_rule.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/controllers/taxes_and_totals.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/database.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/exceptions.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/hooks.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/model.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/selling/__init__.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/selling/proposal.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/selling/quotation.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/selling/sales_order.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/simulation.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/stock/__init__.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/stock/delivery_note.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/stock/purchase_receipt.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/stock/stock_entry.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/stock/stock_ledger.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/lambda_erp/utils.py +0 -0
- {lambda_erp-0.4.0 → lambda_erp-0.5.0}/terraform/README.md +0 -0
|
@@ -10,6 +10,7 @@ Supports multiple chat sessions, each with their own history and auto-generated
|
|
|
10
10
|
|
|
11
11
|
import asyncio
|
|
12
12
|
import base64
|
|
13
|
+
import copy
|
|
13
14
|
import difflib
|
|
14
15
|
import io
|
|
15
16
|
import json
|
|
@@ -1163,6 +1164,44 @@ TOOLS = [
|
|
|
1163
1164
|
},
|
|
1164
1165
|
]
|
|
1165
1166
|
|
|
1167
|
+
|
|
1168
|
+
def _extra_document_slugs():
|
|
1169
|
+
"""Doctype slugs registered beyond the core list (via register_doctype)."""
|
|
1170
|
+
return sorted(s for s in services.SLUG_TO_DOCTYPE if s not in DOCUMENT_SLUGS)
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
def _extra_master_types():
|
|
1174
|
+
"""Master types registered beyond the core list (via register_master)."""
|
|
1175
|
+
return sorted(m for m in services.MASTER_TABLES if m not in MASTER_TYPES)
|
|
1176
|
+
|
|
1177
|
+
|
|
1178
|
+
def build_tools():
|
|
1179
|
+
"""TOOLS with the doctype/master enums widened from the live registries.
|
|
1180
|
+
|
|
1181
|
+
Plugins register doctypes and masters at startup — after this module is
|
|
1182
|
+
imported — so the static TOOLS constant can't know about them. The tool
|
|
1183
|
+
handlers already resolve through the live registries; the enums in the
|
|
1184
|
+
schemas were the only thing shutting registered types out of chat. Built
|
|
1185
|
+
per request (cheap next to the LLM call), which also makes plugin load
|
|
1186
|
+
order irrelevant. Only enums that exactly match the core lists are
|
|
1187
|
+
widened, so any differently-constrained doctype/master param (e.g. a
|
|
1188
|
+
curated subset) is left alone.
|
|
1189
|
+
"""
|
|
1190
|
+
extra_docs = _extra_document_slugs()
|
|
1191
|
+
extra_masters = _extra_master_types()
|
|
1192
|
+
if not extra_docs and not extra_masters:
|
|
1193
|
+
return TOOLS
|
|
1194
|
+
tools = copy.deepcopy(TOOLS)
|
|
1195
|
+
for tool in tools:
|
|
1196
|
+
props = tool["function"].get("parameters", {}).get("properties", {})
|
|
1197
|
+
for schema in props.values():
|
|
1198
|
+
if schema.get("enum") == DOCUMENT_SLUGS:
|
|
1199
|
+
schema["enum"] = DOCUMENT_SLUGS + extra_docs
|
|
1200
|
+
elif schema.get("enum") == MASTER_TYPES:
|
|
1201
|
+
schema["enum"] = MASTER_TYPES + extra_masters
|
|
1202
|
+
return tools
|
|
1203
|
+
|
|
1204
|
+
|
|
1166
1205
|
# ---------------------------------------------------------------------------
|
|
1167
1206
|
# Tool handlers — dispatch to existing service functions
|
|
1168
1207
|
# ---------------------------------------------------------------------------
|
|
@@ -1845,6 +1884,21 @@ def build_system_prompt(user_info: dict | None = None, channel: str = "web"):
|
|
|
1845
1884
|
analytics_context = _prompt_analytics_context()
|
|
1846
1885
|
uom_context = _prompt_uom_context()
|
|
1847
1886
|
|
|
1887
|
+
# Deployment-registered types (register_doctype / register_master). Folded
|
|
1888
|
+
# into the type lists below so the assistant knows they exist and can act
|
|
1889
|
+
# on them from day one — the tool schemas widen the same way (build_tools).
|
|
1890
|
+
extra_docs = _extra_document_slugs()
|
|
1891
|
+
extra_masters = _extra_master_types()
|
|
1892
|
+
extension_doctypes_line = (
|
|
1893
|
+
f"\n- **Extensions (deployment-specific):** {', '.join(extra_docs)}" if extra_docs else ""
|
|
1894
|
+
)
|
|
1895
|
+
master_types_line = ", ".join(MASTER_TYPES + extra_masters)
|
|
1896
|
+
extension_master_keys = "".join(
|
|
1897
|
+
f"\n- **{services.MASTER_TABLES[m][0]}** (master_type `{m}`): key = `name`, "
|
|
1898
|
+
f"display = `{services.MASTER_TABLES[m][1]}`"
|
|
1899
|
+
for m in extra_masters
|
|
1900
|
+
)
|
|
1901
|
+
|
|
1848
1902
|
# Channel-aware link guidance. On the "web" channel the reader is a browser
|
|
1849
1903
|
# inside the ERP, so web-relative links are clickable. On the "api" channel the
|
|
1850
1904
|
# reply is relayed to an EXTERNAL app (lambda-web / the Lambda app) that is not
|
|
@@ -1952,7 +2006,7 @@ When a user asks you to do something they don't have permission for, explain wha
|
|
|
1952
2006
|
- **Buying:** purchase-order, purchase-invoice
|
|
1953
2007
|
- **Accounting:** payment-entry, journal-entry, budget, subscription, bank-transaction
|
|
1954
2008
|
- **Stock:** stock-entry, delivery-note, purchase-receipt
|
|
1955
|
-
- **Settings:** pricing-rule
|
|
2009
|
+
- **Settings:** pricing-rule{extension_doctypes_line}
|
|
1956
2010
|
|
|
1957
2011
|
## Document Workflow & What Each Document Does
|
|
1958
2012
|
|
|
@@ -2076,7 +2130,7 @@ For purchased goods, prefer Purchase Receipt when receiving separately from bill
|
|
|
2076
2130
|
- Cancelled documents cannot be re-submitted or modified.
|
|
2077
2131
|
|
|
2078
2132
|
## Master Data Types
|
|
2079
|
-
|
|
2133
|
+
{master_types_line}
|
|
2080
2134
|
|
|
2081
2135
|
## Master Data Deletion
|
|
2082
2136
|
Master records CAN be deleted — use the `delete_master` tool (ADMIN role only). It is reference-protected: an unreferenced record (e.g. a mistakenly created or duplicate one) is permanently deleted; a record referenced by any document, GL entry, or stock data is NOT deleted but automatically disabled/archived instead, and the result names the blocking reference — relay that reason to the user. When the user merely wants a record out of the way (not destroyed), prefer `update_master` with `{{"disabled": 1}}`. Non-admin users: explain that deletion needs an admin and offer to disable instead.
|
|
@@ -2153,7 +2207,7 @@ Every master record has a primary key (the `name` column) and a human-readable d
|
|
|
2153
2207
|
- **Item:** key = `item_code` (e.g. `SVC-005`), display = `item_name` (e.g. "Project Management")
|
|
2154
2208
|
- **Customer:** key = `name` (e.g. `CUST-007`), display = `customer_name` (e.g. "Redstone Automotive")
|
|
2155
2209
|
- **Supplier:** key = `name` (e.g. `SUPP-003`), display = `supplier_name`
|
|
2156
|
-
- **Warehouse / Company / Account / Cost Center:** key = `name`
|
|
2210
|
+
- **Warehouse / Company / Account / Cost Center:** key = `name`{extension_master_keys}
|
|
2157
2211
|
|
|
2158
2212
|
When you fill in `item_code`, `customer`, `supplier`, `warehouse`, `company`, etc. in a document or child-table row, you MUST use the **primary key**, never the display name. `"item_code": "Project Management"` is ALWAYS wrong — it's a name, not a code.
|
|
2159
2213
|
|
|
@@ -2646,7 +2700,7 @@ async def run_thinking_loop(
|
|
|
2646
2700
|
openai_client.chat.completions.create,
|
|
2647
2701
|
model=model_name,
|
|
2648
2702
|
messages=messages,
|
|
2649
|
-
tools=
|
|
2703
|
+
tools=build_tools(),
|
|
2650
2704
|
tool_choice="auto",
|
|
2651
2705
|
max_completion_tokens=max_completion,
|
|
2652
2706
|
# gpt-5.6-terra rejects function tools on /v1/chat/completions
|
|
@@ -11,6 +11,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
|
|
11
11
|
from fastapi.responses import FileResponse
|
|
12
12
|
from fastapi.staticfiles import StaticFiles
|
|
13
13
|
|
|
14
|
+
from lambda_erp import get_app_version
|
|
14
15
|
from lambda_erp.database import setup
|
|
15
16
|
|
|
16
17
|
from api.errors import register_exception_handlers
|
|
@@ -70,7 +71,7 @@ async def lifespan(app: FastAPI):
|
|
|
70
71
|
|
|
71
72
|
app = FastAPI(
|
|
72
73
|
title="Lambda ERP",
|
|
73
|
-
version=
|
|
74
|
+
version=get_app_version(),
|
|
74
75
|
lifespan=lifespan,
|
|
75
76
|
)
|
|
76
77
|
|
|
@@ -105,7 +106,7 @@ app.include_router(chat_api.router, prefix="/api")
|
|
|
105
106
|
|
|
106
107
|
@app.get("/api/health")
|
|
107
108
|
def health():
|
|
108
|
-
return {"status": "ok"}
|
|
109
|
+
return {"status": "ok", "version": get_app_version()}
|
|
109
110
|
|
|
110
111
|
|
|
111
112
|
@app.websocket("/ws/chat")
|
|
@@ -5,7 +5,10 @@ import re
|
|
|
5
5
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
6
6
|
from lambda_erp.database import get_db
|
|
7
7
|
from lambda_erp.utils import _dict
|
|
8
|
-
|
|
8
|
+
# The per-type registries live in api.services so `register_master` can extend
|
|
9
|
+
# them; imported here (not moved) so existing `from api.routers.masters import
|
|
10
|
+
# MASTER_IDENTITY_ALIAS`-style imports keep working.
|
|
11
|
+
from api.services import MASTER_TABLES, MASTER_NAME_PREFIXES, MASTER_IDENTITY_ALIAS
|
|
9
12
|
from api.auth import require_role, require_non_public_manager
|
|
10
13
|
|
|
11
14
|
router = APIRouter(prefix="/masters", tags=["masters"])
|
|
@@ -14,22 +17,6 @@ _viewer = Depends(require_role("viewer"))
|
|
|
14
17
|
_manager = Depends(require_non_public_manager)
|
|
15
18
|
_admin = Depends(require_role("admin"))
|
|
16
19
|
|
|
17
|
-
MASTER_NAME_PREFIXES = {
|
|
18
|
-
"customer": "CUST",
|
|
19
|
-
"supplier": "SUPP",
|
|
20
|
-
"item": "ITEM",
|
|
21
|
-
"warehouse": "WH",
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
# An Item's code is stored in the primary-key column `name`, but every
|
|
25
|
-
# transaction line and report references it as
|
|
26
|
-
# `item_code`. Accept that intuitive alias on the master API so callers
|
|
27
|
-
# (LLM tools, REST clients) can set and read the code under the name they
|
|
28
|
-
# already use everywhere else, instead of silently falling back to ITEM-NNN.
|
|
29
|
-
MASTER_IDENTITY_ALIAS = {
|
|
30
|
-
"item": "item_code",
|
|
31
|
-
}
|
|
32
|
-
|
|
33
20
|
|
|
34
21
|
def _echo_identity_alias(master_type: str, row):
|
|
35
22
|
"""Mirror the master's `name` back under its intuitive alias (item_code)."""
|
|
@@ -70,6 +70,25 @@ MASTER_TABLES = {
|
|
|
70
70
|
"cost-center": ("Cost Center", "cost_center_name"),
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
# Auto-naming prefixes: masters created without an explicit `name` get a
|
|
74
|
+
# generated PREFIX-NNN id (see masters.create_master_record). Types without an
|
|
75
|
+
# entry require an explicit name (or derive it, like company).
|
|
76
|
+
MASTER_NAME_PREFIXES = {
|
|
77
|
+
"customer": "CUST",
|
|
78
|
+
"supplier": "SUPP",
|
|
79
|
+
"item": "ITEM",
|
|
80
|
+
"warehouse": "WH",
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# An Item's code is stored in the primary-key column `name`, but every
|
|
84
|
+
# transaction line and report references it as `item_code`. Accept that
|
|
85
|
+
# intuitive alias on the master API so callers (LLM tools, REST clients) can
|
|
86
|
+
# set and read the code under the name they already use everywhere else,
|
|
87
|
+
# instead of silently falling back to ITEM-NNN.
|
|
88
|
+
MASTER_IDENTITY_ALIAS = {
|
|
89
|
+
"item": "item_code",
|
|
90
|
+
}
|
|
91
|
+
|
|
73
92
|
# Slug <-> doctype name mapping
|
|
74
93
|
SLUG_TO_DOCTYPE = {}
|
|
75
94
|
DOCTYPE_TO_SLUG = {}
|
|
@@ -95,6 +114,32 @@ def register_doctype(doctype: str, cls, slug: str | None = None) -> None:
|
|
|
95
114
|
DOCTYPE_TO_SLUG[doctype] = slug
|
|
96
115
|
|
|
97
116
|
|
|
117
|
+
def register_master(slug: str, table: str, name_field: str, *,
|
|
118
|
+
name_prefix: str | None = None,
|
|
119
|
+
identity_alias: str | None = None) -> None:
|
|
120
|
+
"""Register (or override) a master type — the master-side counterpart of
|
|
121
|
+
`register_doctype`.
|
|
122
|
+
|
|
123
|
+
A plugin calls this at startup and the master becomes a first-class type
|
|
124
|
+
everywhere `MASTER_TABLES` is consulted: the REST CRUD surface
|
|
125
|
+
(`/api/masters/{slug}`) and the chat tools (search_masters,
|
|
126
|
+
get_master_fields, create/update/delete_master — their schemas and the
|
|
127
|
+
system prompt are built from the live registry per request). Fields are
|
|
128
|
+
never declared: they're introspected from the table at call time, so every
|
|
129
|
+
text column of `table` is immediately searchable.
|
|
130
|
+
|
|
131
|
+
`name_field` is the human display column (e.g. "company_name").
|
|
132
|
+
`name_prefix` enables auto-generated ids (prefix "LEAD" -> LEAD-001) when a
|
|
133
|
+
record is created without an explicit `name`. `identity_alias` lets callers
|
|
134
|
+
address the `name` PK under a friendlier key, like item's `item_code`.
|
|
135
|
+
"""
|
|
136
|
+
MASTER_TABLES[slug] = (table, name_field)
|
|
137
|
+
if name_prefix:
|
|
138
|
+
MASTER_NAME_PREFIXES[slug] = name_prefix
|
|
139
|
+
if identity_alias:
|
|
140
|
+
MASTER_IDENTITY_ALIAS[slug] = identity_alias
|
|
141
|
+
|
|
142
|
+
|
|
98
143
|
def get_document_class(doctype_slug: str):
|
|
99
144
|
"""Get document class from URL slug."""
|
|
100
145
|
doctype = SLUG_TO_DOCTYPE.get(doctype_slug)
|
|
@@ -447,6 +447,7 @@ export const api = {
|
|
|
447
447
|
getPublicManagerStatus: () => request<{ active: boolean; user?: any }>("/auth/public-manager"),
|
|
448
448
|
createPublicManager: () => request<any>("/auth/public-manager", { method: "POST" }),
|
|
449
449
|
removePublicManager: () => request<any>("/auth/public-manager", { method: "DELETE" }),
|
|
450
|
+
getHealth: () => request<{ status: string; version: string }>("/health"),
|
|
450
451
|
getSettings: () => request<Record<string, string>>("/auth/settings"),
|
|
451
452
|
updateSettings: (data: Record<string, string>) =>
|
|
452
453
|
request<Record<string, string>>("/auth/settings", { method: "PUT", body: JSON.stringify(data) }),
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Lambda ERP - Core ERP logic packaged as a standalone library."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_app_version() -> str:
|
|
5
|
+
"""Runtime version of the ERP core.
|
|
6
|
+
|
|
7
|
+
The single human-edited source is the packaging version in `pyproject.toml`,
|
|
8
|
+
which the release process bumps. A wheel/pip install doesn't ship
|
|
9
|
+
`pyproject.toml`, so there we read the installed package metadata (populated
|
|
10
|
+
from that same version at build time). A source checkout (incl. editable
|
|
11
|
+
dev) reads `pyproject.toml` directly, so it's correct without a reinstall.
|
|
12
|
+
Falls back to "dev" when neither is available.
|
|
13
|
+
"""
|
|
14
|
+
import re
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
pyproject = Path(__file__).resolve().parent.parent / "pyproject.toml"
|
|
18
|
+
if pyproject.is_file():
|
|
19
|
+
match = re.search(
|
|
20
|
+
r'^version\s*=\s*"([^"]+)"', pyproject.read_text(encoding="utf-8"), re.MULTILINE
|
|
21
|
+
)
|
|
22
|
+
if match:
|
|
23
|
+
return match.group(1)
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from importlib.metadata import version
|
|
27
|
+
|
|
28
|
+
return version("lambda-erp")
|
|
29
|
+
except Exception:
|
|
30
|
+
return "dev"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
__version__ = get_app_version()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|