lambda-erp 0.8.13__tar.gz → 0.8.14__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.8.13 → lambda_erp-0.8.14}/PKG-INFO +1 -1
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/routers/masters.py +14 -2
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/services.py +12 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/pyproject.toml +1 -1
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/.gitignore +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/LICENSE +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/README.md +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/__init__.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/attachments.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/auth.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/bootstrap.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/chat.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/demo_limits.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/deps.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/errors.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/main.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/oauth.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/pdf.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/providers.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/remarks_md.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/routers/__init__.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/routers/accounting.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/routers/actions.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/routers/admin.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/routers/analytics.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/routers/availability.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/routers/bank_reconciliation.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/routers/chat_api.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/routers/documents.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/routers/mcp.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/routers/proposals.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/routers/reports.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/routers/setup.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/templates/document.html +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/api/templates/proposal.html +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/docs/agents/README.md +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/frontend/LICENSE +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/frontend/README.md +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/frontend/src/api/client.ts +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/__init__.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/__init__.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/bank_transaction.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/budget.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/chart_of_accounts.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/general_ledger.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/journal_entry.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/payment_entry.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/pos_invoice.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/purchase_invoice.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/revaluation.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/sales_invoice.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/setup/__init__.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/setup/engine.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/setup/pack.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/setup/packs/__init__.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/setup/packs/ch.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/setup/packs/de_common.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/setup/packs/de_skr03.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/setup/packs/de_skr04.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/setup/packs/generic.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/setup/profiles.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/setup/spine.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/accounting/subscription.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/assets/__init__.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/assets/asset.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/assets/reservation.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/buying/__init__.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/buying/purchase_order.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/controllers/__init__.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/controllers/currency.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/controllers/defaults.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/controllers/pricing_rule.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/controllers/taxes_and_totals.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/database.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/exceptions.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/hooks.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/model.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/selling/__init__.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/selling/proposal.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/selling/quotation.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/selling/sales_order.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/simulation.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/stock/__init__.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/stock/delivery_note.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/stock/purchase_receipt.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/stock/stock_entry.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/stock/stock_ledger.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/lambda_erp/utils.py +0 -0
- {lambda_erp-0.8.13 → lambda_erp-0.8.14}/terraform/README.md +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Master data CRUD: Customer, Supplier, Item, Warehouse, Account, Company."""
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
|
+
import secrets
|
|
4
5
|
|
|
5
6
|
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
|
6
7
|
from lambda_erp.database import get_db
|
|
@@ -9,7 +10,7 @@ from lambda_erp.utils import _dict, now
|
|
|
9
10
|
# them; imported here (not moved) so existing `from api.routers.masters import
|
|
10
11
|
# MASTER_IDENTITY_ALIAS`-style imports keep working.
|
|
11
12
|
from api.services import (
|
|
12
|
-
MASTER_TABLES, MASTER_NAME_PREFIXES, MASTER_IDENTITY_ALIAS,
|
|
13
|
+
MASTER_TABLES, MASTER_NAME_PREFIXES, MASTER_RANDOM_NAME_TYPES, MASTER_IDENTITY_ALIAS,
|
|
13
14
|
MASTER_REFERENCE_CHECKS,
|
|
14
15
|
_search_clause, _where_from_filters, master_search_columns, count_query_cached,
|
|
15
16
|
)
|
|
@@ -183,6 +184,14 @@ def _generate_master_name(db, doctype: str, prefix: str) -> str:
|
|
|
183
184
|
return f"{prefix}-{number:03d}"
|
|
184
185
|
|
|
185
186
|
|
|
187
|
+
def _generate_random_master_name(db, doctype: str, prefix: str) -> str:
|
|
188
|
+
"""Opaque, index-scale id for high-volume registered masters."""
|
|
189
|
+
while True:
|
|
190
|
+
name = f"{prefix}-{secrets.token_hex(8).upper()}"
|
|
191
|
+
if not db.exists(doctype, name):
|
|
192
|
+
return name
|
|
193
|
+
|
|
194
|
+
|
|
186
195
|
def _normalize_master_data(data: dict) -> dict:
|
|
187
196
|
normalized = _dict(data)
|
|
188
197
|
for key, value in list(normalized.items()):
|
|
@@ -218,7 +227,10 @@ def create_master_record(master_type: str, data: dict) -> dict:
|
|
|
218
227
|
if not doc.get("name"):
|
|
219
228
|
prefix = MASTER_NAME_PREFIXES.get(master_type)
|
|
220
229
|
if prefix:
|
|
221
|
-
|
|
230
|
+
if master_type in MASTER_RANDOM_NAME_TYPES:
|
|
231
|
+
doc["name"] = _generate_random_master_name(db, doctype, prefix)
|
|
232
|
+
else:
|
|
233
|
+
doc["name"] = _generate_master_name(db, doctype, prefix)
|
|
222
234
|
elif master_type == "company" and doc.get("company_name"):
|
|
223
235
|
# A company's id is conventionally its name (mirrors /setup/company).
|
|
224
236
|
doc["name"] = doc["company_name"]
|
|
@@ -90,6 +90,11 @@ MASTER_NAME_PREFIXES = {
|
|
|
90
90
|
"warehouse": "WH",
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
# High-volume plugin masters can opt out of sequential ids. Sequential naming
|
|
94
|
+
# must inspect existing suffixes after a process restart; opaque random ids keep
|
|
95
|
+
# inserts constant-time for index-scale reference tables.
|
|
96
|
+
MASTER_RANDOM_NAME_TYPES: set[str] = set()
|
|
97
|
+
|
|
93
98
|
# An Item's code is stored in the primary-key column `name`, but every
|
|
94
99
|
# transaction line and report references it as `item_code`. Accept that
|
|
95
100
|
# intuitive alias on the master API so callers (LLM tools, REST clients) can
|
|
@@ -213,6 +218,7 @@ def document_columns(doctype_slug: str) -> set:
|
|
|
213
218
|
|
|
214
219
|
def register_master(slug: str, table: str, name_field: str, *,
|
|
215
220
|
name_prefix: str | None = None,
|
|
221
|
+
random_name: bool = False,
|
|
216
222
|
identity_alias: str | None = None,
|
|
217
223
|
description: str | None = None,
|
|
218
224
|
fields: list[str] | None = None,
|
|
@@ -236,6 +242,12 @@ def register_master(slug: str, table: str, name_field: str, *,
|
|
|
236
242
|
MASTER_TABLES[slug] = (table, name_field)
|
|
237
243
|
if name_prefix:
|
|
238
244
|
MASTER_NAME_PREFIXES[slug] = name_prefix
|
|
245
|
+
if random_name:
|
|
246
|
+
if not name_prefix:
|
|
247
|
+
raise ValueError("random_name requires name_prefix")
|
|
248
|
+
MASTER_RANDOM_NAME_TYPES.add(slug)
|
|
249
|
+
else:
|
|
250
|
+
MASTER_RANDOM_NAME_TYPES.discard(slug)
|
|
239
251
|
if identity_alias:
|
|
240
252
|
MASTER_IDENTITY_ALIAS[slug] = identity_alias
|
|
241
253
|
if description or fields:
|
|
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
|
|
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
|