lambda-erp 0.6.20__tar.gz → 0.6.22__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.6.20 → lambda_erp-0.6.22}/PKG-INFO +1 -1
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/errors.py +55 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/routers/masters.py +4 -1
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/pyproject.toml +1 -1
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/.gitignore +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/LICENSE +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/README.md +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/__init__.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/attachments.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/auth.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/bootstrap.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/chat.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/demo_limits.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/deps.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/main.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/oauth.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/pdf.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/providers.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/remarks_md.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/routers/__init__.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/routers/accounting.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/routers/admin.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/routers/analytics.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/routers/bank_reconciliation.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/routers/chat_api.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/routers/documents.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/routers/mcp.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/routers/proposals.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/routers/reports.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/routers/setup.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/services.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/templates/document.html +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/api/templates/proposal.html +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/docs/agents/README.md +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/frontend/LICENSE +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/frontend/README.md +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/frontend/src/api/client.ts +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/__init__.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/__init__.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/bank_transaction.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/budget.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/chart_of_accounts.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/general_ledger.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/journal_entry.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/payment_entry.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/pos_invoice.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/purchase_invoice.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/revaluation.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/sales_invoice.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/setup/__init__.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/setup/engine.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/setup/pack.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/setup/packs/__init__.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/setup/packs/ch.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/setup/packs/de_common.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/setup/packs/de_skr03.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/setup/packs/de_skr04.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/setup/packs/generic.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/setup/profiles.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/setup/spine.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/accounting/subscription.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/buying/__init__.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/buying/purchase_order.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/controllers/__init__.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/controllers/currency.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/controllers/defaults.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/controllers/pricing_rule.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/controllers/taxes_and_totals.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/database.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/exceptions.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/hooks.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/model.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/selling/__init__.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/selling/proposal.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/selling/quotation.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/selling/sales_order.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/simulation.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/stock/__init__.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/stock/delivery_note.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/stock/purchase_receipt.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/stock/stock_entry.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/stock/stock_ledger.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/lambda_erp/utils.py +0 -0
- {lambda_erp-0.6.20 → lambda_erp-0.6.22}/terraform/README.md +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Map lambda_erp exceptions to HTTP responses."""
|
|
2
2
|
|
|
3
|
+
import re
|
|
4
|
+
|
|
3
5
|
from fastapi import FastAPI, Request
|
|
4
6
|
from fastapi.responses import JSONResponse
|
|
5
7
|
from lambda_erp.exceptions import (
|
|
@@ -14,6 +16,56 @@ from lambda_erp.exceptions import (
|
|
|
14
16
|
)
|
|
15
17
|
|
|
16
18
|
|
|
19
|
+
# --- DB integrity violations -------------------------------------------------
|
|
20
|
+
# A unique/foreign-key violation raised by the driver (psycopg on Postgres,
|
|
21
|
+
# sqlite3 on SQLite) has no handler below, so it fell through to FastAPI's opaque
|
|
22
|
+
# 500 "Internal Server Error" — which a form may show as a stray red line or
|
|
23
|
+
# swallow entirely (e.g. "saving the UID does nothing, no warning"). Map it to a
|
|
24
|
+
# clear 409 with the colliding field/value so the UI can actually explain it.
|
|
25
|
+
|
|
26
|
+
_PG_KEY_RE = re.compile(r"Key \(([^)]+)\)=\(([^)]*)\)")
|
|
27
|
+
_SQLITE_UNIQUE_RE = re.compile(r"UNIQUE constraint failed: [^.]+\.([^\s,]+)")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _integrity_error_types() -> list:
|
|
31
|
+
"""The driver IntegrityError classes that are actually installed (SQLite
|
|
32
|
+
always; psycopg only with the [postgres] extra). Import defensively so a
|
|
33
|
+
SQLite-only deployment doesn't need psycopg."""
|
|
34
|
+
types: list = []
|
|
35
|
+
try:
|
|
36
|
+
import sqlite3
|
|
37
|
+
types.append(sqlite3.IntegrityError)
|
|
38
|
+
except Exception:
|
|
39
|
+
pass
|
|
40
|
+
for mod_name in ("psycopg", "psycopg2"):
|
|
41
|
+
try:
|
|
42
|
+
mod = __import__(mod_name)
|
|
43
|
+
err = getattr(mod, "IntegrityError", None) or getattr(
|
|
44
|
+
getattr(mod, "errors", None), "IntegrityError", None
|
|
45
|
+
)
|
|
46
|
+
if err is not None:
|
|
47
|
+
types.append(err)
|
|
48
|
+
except Exception:
|
|
49
|
+
pass
|
|
50
|
+
return types
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _friendly_integrity(msg: str) -> str:
|
|
54
|
+
m = _PG_KEY_RE.search(msg or "")
|
|
55
|
+
if m:
|
|
56
|
+
return f"A record with {m.group(1)} = “{m.group(2)}” already exists."
|
|
57
|
+
m = _SQLITE_UNIQUE_RE.search(msg or "")
|
|
58
|
+
if m:
|
|
59
|
+
return f"A record with this {m.group(1)} already exists."
|
|
60
|
+
if "foreign key" in (msg or "").lower():
|
|
61
|
+
return "This links to a record that doesn't exist."
|
|
62
|
+
return "A record with these details already exists (a unique field is duplicated)."
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def _integrity_handler(request: Request, exc: Exception):
|
|
66
|
+
return JSONResponse(status_code=409, content={"detail": _friendly_integrity(str(exc))})
|
|
67
|
+
|
|
68
|
+
|
|
17
69
|
def register_exception_handlers(app: FastAPI):
|
|
18
70
|
|
|
19
71
|
@app.exception_handler(ValueError)
|
|
@@ -54,3 +106,6 @@ def register_exception_handlers(app: FastAPI):
|
|
|
54
106
|
@app.exception_handler(InsufficientFunds)
|
|
55
107
|
async def insufficient_funds_error(request: Request, exc: InsufficientFunds):
|
|
56
108
|
return JSONResponse(status_code=422, content={"detail": str(exc)})
|
|
109
|
+
|
|
110
|
+
for _exc_type in _integrity_error_types():
|
|
111
|
+
app.add_exception_handler(_exc_type, _integrity_handler)
|
|
@@ -319,9 +319,12 @@ def search_masters(master_type: str, q: str = "", _user: dict = _viewer):
|
|
|
319
319
|
if not q:
|
|
320
320
|
return db.get_all(doctype, filters=_with_active_filter(db, doctype), fields=["name", name_field], limit=10)
|
|
321
321
|
|
|
322
|
+
# Case-INSENSITIVE: on Postgres plain LIKE is case-sensitive, so "plus medica"
|
|
323
|
+
# would miss "Plus Medica AG". LOWER(...) LIKE LOWER(...) is portable (SQLite
|
|
324
|
+
# + Postgres) and matches how the document search (_search_clause) works.
|
|
322
325
|
rows = db.sql(
|
|
323
326
|
f'SELECT name, "{name_field}" FROM "{doctype}" '
|
|
324
|
-
f'WHERE {active_prefix}(name LIKE ? OR "{name_field}" LIKE ?) LIMIT 10',
|
|
327
|
+
f'WHERE {active_prefix}(LOWER(name) LIKE LOWER(?) OR LOWER("{name_field}") LIKE LOWER(?)) LIMIT 10',
|
|
325
328
|
[f"%{q}%", f"%{q}%"],
|
|
326
329
|
)
|
|
327
330
|
return rows
|
|
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
|