lambda-erp 0.6.2__tar.gz → 0.6.3__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 (83) hide show
  1. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/PKG-INFO +1 -1
  2. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/chat.py +10 -0
  3. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/main.py +21 -0
  4. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/services.py +39 -3
  5. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/pyproject.toml +1 -1
  6. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/.gitignore +0 -0
  7. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/LICENSE +0 -0
  8. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/README.md +0 -0
  9. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/__init__.py +0 -0
  10. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/attachments.py +0 -0
  11. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/auth.py +0 -0
  12. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/bootstrap.py +0 -0
  13. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/demo_limits.py +0 -0
  14. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/deps.py +0 -0
  15. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/errors.py +0 -0
  16. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/oauth.py +0 -0
  17. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/pdf.py +0 -0
  18. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/providers.py +0 -0
  19. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/remarks_md.py +0 -0
  20. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/routers/__init__.py +0 -0
  21. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/routers/accounting.py +0 -0
  22. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/routers/admin.py +0 -0
  23. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/routers/analytics.py +0 -0
  24. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/routers/bank_reconciliation.py +0 -0
  25. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/routers/chat_api.py +0 -0
  26. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/routers/documents.py +0 -0
  27. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/routers/masters.py +0 -0
  28. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/routers/proposals.py +0 -0
  29. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/routers/reports.py +0 -0
  30. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/routers/setup.py +0 -0
  31. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/templates/document.html +0 -0
  32. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/api/templates/proposal.html +0 -0
  33. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/docs/agents/README.md +0 -0
  34. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/frontend/LICENSE +0 -0
  35. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/frontend/README.md +0 -0
  36. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/frontend/src/api/client.ts +0 -0
  37. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/__init__.py +0 -0
  38. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/__init__.py +0 -0
  39. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/bank_transaction.py +0 -0
  40. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/budget.py +0 -0
  41. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/chart_of_accounts.py +0 -0
  42. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/general_ledger.py +0 -0
  43. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/journal_entry.py +0 -0
  44. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/payment_entry.py +0 -0
  45. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/pos_invoice.py +0 -0
  46. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/purchase_invoice.py +0 -0
  47. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/revaluation.py +0 -0
  48. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/sales_invoice.py +0 -0
  49. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/setup/__init__.py +0 -0
  50. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/setup/engine.py +0 -0
  51. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/setup/pack.py +0 -0
  52. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/setup/packs/__init__.py +0 -0
  53. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/setup/packs/ch.py +0 -0
  54. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/setup/packs/de_common.py +0 -0
  55. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/setup/packs/de_skr03.py +0 -0
  56. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/setup/packs/de_skr04.py +0 -0
  57. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/setup/packs/generic.py +0 -0
  58. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/setup/profiles.py +0 -0
  59. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/setup/spine.py +0 -0
  60. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/accounting/subscription.py +0 -0
  61. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/buying/__init__.py +0 -0
  62. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/buying/purchase_order.py +0 -0
  63. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/controllers/__init__.py +0 -0
  64. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/controllers/currency.py +0 -0
  65. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/controllers/defaults.py +0 -0
  66. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/controllers/pricing_rule.py +0 -0
  67. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/controllers/taxes_and_totals.py +0 -0
  68. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/database.py +0 -0
  69. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/exceptions.py +0 -0
  70. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/hooks.py +0 -0
  71. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/model.py +0 -0
  72. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/selling/__init__.py +0 -0
  73. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/selling/proposal.py +0 -0
  74. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/selling/quotation.py +0 -0
  75. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/selling/sales_order.py +0 -0
  76. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/simulation.py +0 -0
  77. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/stock/__init__.py +0 -0
  78. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/stock/delivery_note.py +0 -0
  79. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/stock/purchase_receipt.py +0 -0
  80. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/stock/stock_entry.py +0 -0
  81. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/stock/stock_ledger.py +0 -0
  82. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/lambda_erp/utils.py +0 -0
  83. {lambda_erp-0.6.2 → lambda_erp-0.6.3}/terraform/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lambda-erp
3
- Version: 0.6.2
3
+ Version: 0.6.3
4
4
  Summary: Core ERP logic - accounting, sales, purchasing, inventory
5
5
  Author: TORUS INVESTMENTS AG
6
6
  License-Expression: Apache-2.0
@@ -1198,6 +1198,16 @@ def _chat_doctype_section() -> str:
1198
1198
  if links:
1199
1199
  link_str = ", ".join(f"`{f}` → the {t}'s name" for f, t in links.items())
1200
1200
  line += f" Links: {link_str}."
1201
+ # How to open a record of this type (so links resolve).
1202
+ page = services.chat_doctype_page_info(slug)
1203
+ if page and page["kind"] == "self":
1204
+ line += f" Open a record at `/app/{slug}/<name>`."
1205
+ elif page and page["kind"] == "via":
1206
+ line += (f" It has NO page of its own — open it via its parent: "
1207
+ f"`/app/{page['parent_slug']}/<{page['link_field']}>`; "
1208
+ f"never link `/app/{slug}/…`.")
1209
+ elif page and page["kind"] == "none":
1210
+ line += " It has no page — do not emit a link for it."
1201
1211
  lines.append(line)
1202
1212
  return (
1203
1213
  "\n\n## Custom record types (deployment-specific)\n"
@@ -121,6 +121,27 @@ def health():
121
121
  return {"status": "ok", "version": get_app_version()}
122
122
 
123
123
 
124
+ @app.get("/api/chat-doctypes")
125
+ def chat_doctypes():
126
+ """Public metadata for doctypes registered via register_chat_doctype: their
127
+ description and, for the frontend, how each is opened (`page`). The frontend
128
+ uses the `page` of kind "via" to auto-register a redirect from the doctype's
129
+ (chat-generated) `/app/{slug}/{name}` link to its parent's page. Not
130
+ sensitive — it's the same info the chat system prompt carries."""
131
+ from api import services
132
+ out = []
133
+ for slug, meta in services.CHAT_DOCTYPES.items():
134
+ if slug not in services.SLUG_TO_DOCTYPE:
135
+ continue
136
+ out.append({
137
+ "slug": slug,
138
+ "description": meta.get("description"),
139
+ "fields": meta.get("fields", []),
140
+ "page": services.chat_doctype_page_info(slug),
141
+ })
142
+ return {"doctypes": out}
143
+
144
+
124
145
  @app.websocket("/ws/chat")
125
146
  async def ws_chat(websocket: WebSocket):
126
147
  # Authenticate via cookie, fall back to public_manager for demo mode
@@ -225,12 +225,48 @@ def register_master(slug: str, table: str, name_field: str, *,
225
225
  CHAT_DOCTYPES: dict = {}
226
226
 
227
227
 
228
- def register_chat_doctype(slug: str, *, description: str, fields: list | None = None) -> None:
228
+ def register_chat_doctype(slug: str, *, description: str, fields: list | None = None,
229
+ page: str | None = "self") -> None:
229
230
  """Give the AI chat a description (and optional key-field hints) for a doctype
230
231
  already registered via `register_doctype`, so build_system_prompt can tell the
231
232
  model what it is and how it links (LINK_FIELDS are surfaced automatically).
232
- No new tools — the document tools already cover every registered doctype."""
233
- CHAT_DOCTYPES[slug] = {"description": description, "fields": fields or []}
233
+ No new tools — the document tools already cover every registered doctype.
234
+
235
+ `page` tells the chat how a record of this doctype is *opened*, so it emits
236
+ correct view links (and the frontend can auto-register redirects):
237
+ "self" (default) — its own page at `/app/{slug}/{name}`;
238
+ "<link_field>" — no page of its own; it's shown inside the parent that
239
+ `link_field` (a LINK_FIELD) references, so it opens at
240
+ `/app/{parent-slug}/{value of link_field}` (e.g. a
241
+ contact opens via its lead: page="lead_id");
242
+ None — no page and no view link at all.
243
+ """
244
+ CHAT_DOCTYPES[slug] = {"description": description, "fields": fields or [], "page": page}
245
+
246
+
247
+ def chat_doctype_page_info(slug: str) -> dict | None:
248
+ """Resolve a chat-doctype's `page` config into a link rule, for the system
249
+ prompt and the /api/chat-doctypes read. Returns one of:
250
+ {"kind": "self", "slug": slug}
251
+ {"kind": "via", "link_field": f, "parent_slug": p} # opens via a parent
252
+ {"kind": "none"}
253
+ or None if the slug isn't a registered chat-doctype."""
254
+ meta = CHAT_DOCTYPES.get(slug)
255
+ if not meta:
256
+ return None
257
+ page = meta.get("page", "self")
258
+ if page == "self":
259
+ return {"kind": "self", "slug": slug}
260
+ if page is None:
261
+ return {"kind": "none"}
262
+ # page names a LINK_FIELD -> resolve the parent doctype -> its slug.
263
+ doctype = SLUG_TO_DOCTYPE.get(slug)
264
+ cls = DOCUMENT_CLASSES.get(doctype) if doctype else None
265
+ links = getattr(cls, "LINK_FIELDS", None) or {}
266
+ parent_slug = DOCTYPE_TO_SLUG.get(links.get(page)) if links.get(page) else None
267
+ if not parent_slug:
268
+ return {"kind": "self", "slug": slug} # misconfigured -> safe fallback
269
+ return {"kind": "via", "link_field": page, "parent_slug": parent_slug}
234
270
 
235
271
 
236
272
  def get_document_class(doctype_slug: str):
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lambda-erp"
3
- version = "0.6.2"
3
+ version = "0.6.3"
4
4
  description = "Core ERP logic - accounting, sales, purchasing, inventory"
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
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