lambda-erp 0.2.5__tar.gz → 0.2.7__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.
Files changed (82) hide show
  1. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/PKG-INFO +1 -1
  2. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/chat.py +17 -4
  3. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/main.py +27 -2
  4. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/pyproject.toml +1 -1
  5. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/.gitignore +0 -0
  6. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/LICENSE +0 -0
  7. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/README.md +0 -0
  8. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/__init__.py +0 -0
  9. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/attachments.py +0 -0
  10. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/auth.py +0 -0
  11. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/bootstrap.py +0 -0
  12. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/demo_limits.py +0 -0
  13. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/deps.py +0 -0
  14. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/errors.py +0 -0
  15. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/oauth.py +0 -0
  16. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/pdf.py +0 -0
  17. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/providers.py +0 -0
  18. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/remarks_md.py +0 -0
  19. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/routers/__init__.py +0 -0
  20. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/routers/accounting.py +0 -0
  21. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/routers/admin.py +0 -0
  22. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/routers/analytics.py +0 -0
  23. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/routers/bank_reconciliation.py +0 -0
  24. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/routers/chat_api.py +0 -0
  25. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/routers/documents.py +0 -0
  26. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/routers/masters.py +0 -0
  27. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/routers/proposals.py +0 -0
  28. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/routers/reports.py +0 -0
  29. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/routers/setup.py +0 -0
  30. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/services.py +0 -0
  31. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/templates/document.html +0 -0
  32. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/api/templates/proposal.html +0 -0
  33. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/docs/agents/README.md +0 -0
  34. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/frontend/README.md +0 -0
  35. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/frontend/src/api/client.ts +0 -0
  36. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/__init__.py +0 -0
  37. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/__init__.py +0 -0
  38. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/bank_transaction.py +0 -0
  39. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/budget.py +0 -0
  40. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/chart_of_accounts.py +0 -0
  41. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/general_ledger.py +0 -0
  42. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/journal_entry.py +0 -0
  43. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/payment_entry.py +0 -0
  44. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/pos_invoice.py +0 -0
  45. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/purchase_invoice.py +0 -0
  46. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/revaluation.py +0 -0
  47. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/sales_invoice.py +0 -0
  48. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/setup/__init__.py +0 -0
  49. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/setup/engine.py +0 -0
  50. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/setup/pack.py +0 -0
  51. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/setup/packs/__init__.py +0 -0
  52. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/setup/packs/ch.py +0 -0
  53. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/setup/packs/de_common.py +0 -0
  54. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/setup/packs/de_skr03.py +0 -0
  55. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/setup/packs/de_skr04.py +0 -0
  56. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/setup/packs/generic.py +0 -0
  57. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/setup/profiles.py +0 -0
  58. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/setup/spine.py +0 -0
  59. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/accounting/subscription.py +0 -0
  60. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/buying/__init__.py +0 -0
  61. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/buying/purchase_order.py +0 -0
  62. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/controllers/__init__.py +0 -0
  63. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/controllers/currency.py +0 -0
  64. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/controllers/defaults.py +0 -0
  65. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/controllers/pricing_rule.py +0 -0
  66. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/controllers/taxes_and_totals.py +0 -0
  67. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/database.py +0 -0
  68. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/exceptions.py +0 -0
  69. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/hooks.py +0 -0
  70. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/model.py +0 -0
  71. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/selling/__init__.py +0 -0
  72. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/selling/proposal.py +0 -0
  73. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/selling/quotation.py +0 -0
  74. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/selling/sales_order.py +0 -0
  75. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/simulation.py +0 -0
  76. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/stock/__init__.py +0 -0
  77. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/stock/delivery_note.py +0 -0
  78. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/stock/purchase_receipt.py +0 -0
  79. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/stock/stock_entry.py +0 -0
  80. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/stock/stock_ledger.py +0 -0
  81. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/lambda_erp/utils.py +0 -0
  82. {lambda_erp-0.2.5 → lambda_erp-0.2.7}/terraform/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lambda-erp
3
- Version: 0.2.5
3
+ Version: 0.2.7
4
4
  Summary: Core ERP logic - accounting, sales, purchasing, inventory
5
5
  Author: TORUS INVESTMENTS AG
6
6
  License-Expression: MIT
@@ -583,7 +583,7 @@ TOOLS = [
583
583
  "type": "function",
584
584
  "function": {
585
585
  "name": "search_masters",
586
- "description": "Search master data (customers, suppliers, items, warehouses, accounts, companies, cost centers). Case-insensitive, with fuzzy fallback for misspellings. By default matches across standard text fields (name, display name, address/city/zip); large free-text columns like description/templates are skipped unless named in `fields`. PREFER passing `fields` whenever you know which attribute you're matching on (e.g. a city, an email, a tax id) — it's faster, more precise, and avoids false hits from other columns. Returns matching records.",
586
+ "description": "Search master data (customers, suppliers, items, warehouses, accounts, companies, cost centers). Case-insensitive, with fuzzy fallback for misspellings. By default matches across standard text fields (name, display name, address/city/zip); large free-text columns like description/templates are skipped unless named in `fields`. PREFER passing `fields` whenever you know which attribute you're matching on (e.g. a city, an email, a tax id) — it's faster, more precise, and avoids false hits from other columns. Returns ALL matching records by default (no cap); pass `limit` only to bound a large list.",
587
587
  "parameters": {
588
588
  "type": "object",
589
589
  "properties": {
@@ -594,6 +594,7 @@ TOOLS = [
594
594
  "items": {"type": "string"},
595
595
  "description": "Recommended: the column(s) to search, e.g. [\"city\"] or [\"customer_name\"]. Narrowing here is faster and avoids matching unrelated columns. Omit only when you genuinely don't know which field holds the value, to search all standard text fields.",
596
596
  },
597
+ "limit": {"type": "integer", "description": "Optional max number of results. OMIT for no cap — returns ALL matches (e.g. to see the entire chart of accounts). Pass a number only to bound a large list."},
597
598
  },
598
599
  "required": ["master_type"],
599
600
  },
@@ -1308,6 +1309,17 @@ def _handle_search_masters(args):
1308
1309
  master_type = args["master_type"]
1309
1310
  query = (args.get("query") or "").strip()
1310
1311
 
1312
+ # Optional result cap the caller sets per call; omit for NO cap (return all —
1313
+ # e.g. the whole chart of accounts). Coerced to int so the LIMIT clause below
1314
+ # can't be injected.
1315
+ _raw_limit = args.get("limit")
1316
+ limit = None
1317
+ if _raw_limit not in (None, "", 0):
1318
+ try:
1319
+ limit = max(1, int(_raw_limit))
1320
+ except (TypeError, ValueError):
1321
+ limit = None
1322
+
1311
1323
  entry = services.MASTER_TABLES.get(master_type)
1312
1324
  if not entry:
1313
1325
  return {"error": f"Unknown master type: {master_type}"}
@@ -1317,7 +1329,7 @@ def _handle_search_masters(args):
1317
1329
 
1318
1330
  if not query:
1319
1331
  filters = {"disabled": 0} if has_disabled else None
1320
- return db.get_all(doctype, filters=filters, fields=["*"], limit=20)
1332
+ return db.get_all(doctype, filters=filters, fields=["*"], limit=limit)
1321
1333
 
1322
1334
  # Optional `fields` narrows the search to specific columns — cheaper, more
1323
1335
  # precise, and the only way to reach bulk columns (description, templates)
@@ -1338,15 +1350,16 @@ def _handle_search_masters(args):
1338
1350
  # silently broke prod search); CAST lets targeted non-text columns match too.
1339
1351
  where = " OR ".join(f'lower(CAST("{col}" AS TEXT)) LIKE ?' for col in search_cols)
1340
1352
  pattern = f"%{query.lower()}%"
1353
+ limit_sql = f" LIMIT {limit}" if limit else ""
1341
1354
  rows = db.sql(
1342
- f'SELECT * FROM "{doctype}" WHERE {active_prefix}({where}) LIMIT 20',
1355
+ f'SELECT * FROM "{doctype}" WHERE {active_prefix}({where}){limit_sql}',
1343
1356
  [pattern] * len(search_cols),
1344
1357
  )
1345
1358
  if rows:
1346
1359
  return [dict(r) for r in rows]
1347
1360
 
1348
1361
  # Nothing matched literally — try fuzzy matching to catch misspellings.
1349
- return _fuzzy_master_search(db, doctype, search_cols, query, has_disabled, limit=20)
1362
+ return _fuzzy_master_search(db, doctype, search_cols, query, has_disabled, limit=limit)
1350
1363
 
1351
1364
 
1352
1365
  def _ignored_master_fields(master_type: str, data: dict) -> list[str]:
@@ -158,11 +158,29 @@ async def ws_chat(websocket: WebSocket):
158
158
  # semantics: real files (assets, favicon, logos) resolve as-is; anything
159
159
  # else falls through to index.html and React Router handles it
160
160
  # client-side. API and WebSocket paths keep returning their normal 404s.
161
+ class _ImmutableStaticFiles(StaticFiles):
162
+ """Serve content-hashed build assets with a long-lived immutable cache.
163
+
164
+ Vite fingerprints every file under /assets (index-<hash>.js, etc.), so the
165
+ bytes at a given URL never change — a new build emits new filenames. The
166
+ `immutable` directive lets the browser skip revalidation entirely (no 304
167
+ round-trip on reload), while index.html stays `no-cache` so a new build is
168
+ still picked up. Net effect vs. the old default: fewer requests, not more.
169
+ """
170
+
171
+ async def get_response(self, path, scope):
172
+ response = await super().get_response(path, scope)
173
+ response.headers.setdefault(
174
+ "Cache-Control", "public, max-age=31536000, immutable"
175
+ )
176
+ return response
177
+
178
+
161
179
  frontend_dist = os.path.join(os.path.dirname(__file__), "..", "frontend", "dist")
162
180
  if os.path.isdir(frontend_dist):
163
181
  assets_dir = os.path.join(frontend_dist, "assets")
164
182
  if os.path.isdir(assets_dir):
165
- app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
183
+ app.mount("/assets", _ImmutableStaticFiles(directory=assets_dir), name="assets")
166
184
 
167
185
  frontend_root = os.path.realpath(frontend_dist)
168
186
  index_html = os.path.join(frontend_root, "index.html")
@@ -183,4 +201,11 @@ if os.path.isdir(frontend_dist):
183
201
  return FileResponse(candidate)
184
202
 
185
203
  # SPA fallback — React Router takes over from here.
186
- return FileResponse(index_html)
204
+ #
205
+ # `no-cache` = always revalidate the shell before reuse (a cheap 304
206
+ # when unchanged, fresh bytes after a deploy). index.html is the only
207
+ # UNHASHED file and it points at the hashed /assets bundles, so if the
208
+ # browser heuristically caches it (which it does when no Cache-Control
209
+ # is set), a soft reload serves a stale shell referencing old asset
210
+ # URLs — the "refresh doesn't refresh, need a hard-refresh" bug.
211
+ return FileResponse(index_html, headers={"Cache-Control": "no-cache"})
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lambda-erp"
3
- version = "0.2.5"
3
+ version = "0.2.7"
4
4
  description = "Core ERP logic - accounting, sales, purchasing, inventory"
5
5
  readme = "README.md"
6
6
  license = "MIT"
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