lambda-erp 0.1.42__tar.gz → 0.1.43__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 (71) hide show
  1. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/PKG-INFO +1 -1
  2. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/chat.py +42 -2
  3. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/routers/chat_api.py +14 -2
  4. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/pyproject.toml +1 -1
  5. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/.gitignore +0 -0
  6. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/LICENSE +0 -0
  7. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/README.md +0 -0
  8. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/__init__.py +0 -0
  9. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/attachments.py +0 -0
  10. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/auth.py +0 -0
  11. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/bootstrap.py +0 -0
  12. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/demo_limits.py +0 -0
  13. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/deps.py +0 -0
  14. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/errors.py +0 -0
  15. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/main.py +0 -0
  16. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/oauth.py +0 -0
  17. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/pdf.py +0 -0
  18. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/providers.py +0 -0
  19. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/remarks_md.py +0 -0
  20. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/routers/__init__.py +0 -0
  21. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/routers/accounting.py +0 -0
  22. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/routers/admin.py +0 -0
  23. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/routers/analytics.py +0 -0
  24. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/routers/bank_reconciliation.py +0 -0
  25. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/routers/documents.py +0 -0
  26. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/routers/masters.py +0 -0
  27. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/routers/proposals.py +0 -0
  28. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/routers/reports.py +0 -0
  29. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/routers/setup.py +0 -0
  30. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/services.py +0 -0
  31. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/templates/document.html +0 -0
  32. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/api/templates/proposal.html +0 -0
  33. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/docs/agents/README.md +0 -0
  34. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/frontend/README.md +0 -0
  35. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/frontend/src/api/client.ts +0 -0
  36. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/__init__.py +0 -0
  37. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/accounting/__init__.py +0 -0
  38. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/accounting/bank_transaction.py +0 -0
  39. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/accounting/budget.py +0 -0
  40. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/accounting/chart_of_accounts.py +0 -0
  41. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/accounting/general_ledger.py +0 -0
  42. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/accounting/journal_entry.py +0 -0
  43. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/accounting/payment_entry.py +0 -0
  44. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/accounting/pos_invoice.py +0 -0
  45. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/accounting/purchase_invoice.py +0 -0
  46. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/accounting/revaluation.py +0 -0
  47. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/accounting/sales_invoice.py +0 -0
  48. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/accounting/subscription.py +0 -0
  49. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/buying/__init__.py +0 -0
  50. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/buying/purchase_order.py +0 -0
  51. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/controllers/__init__.py +0 -0
  52. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/controllers/currency.py +0 -0
  53. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/controllers/defaults.py +0 -0
  54. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/controllers/pricing_rule.py +0 -0
  55. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/controllers/taxes_and_totals.py +0 -0
  56. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/database.py +0 -0
  57. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/exceptions.py +0 -0
  58. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/hooks.py +0 -0
  59. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/model.py +0 -0
  60. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/selling/__init__.py +0 -0
  61. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/selling/proposal.py +0 -0
  62. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/selling/quotation.py +0 -0
  63. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/selling/sales_order.py +0 -0
  64. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/simulation.py +0 -0
  65. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/stock/__init__.py +0 -0
  66. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/stock/delivery_note.py +0 -0
  67. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/stock/purchase_receipt.py +0 -0
  68. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/stock/stock_entry.py +0 -0
  69. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/stock/stock_ledger.py +0 -0
  70. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/lambda_erp/utils.py +0 -0
  71. {lambda_erp-0.1.42 → lambda_erp-0.1.43}/terraform/README.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lambda-erp
3
- Version: 0.1.42
3
+ Version: 0.1.43
4
4
  Summary: Core ERP logic - accounting, sales, purchasing, inventory
5
5
  Author: TORUS INVESTMENTS AG
6
6
  License-Expression: MIT
@@ -1788,6 +1788,13 @@ When you parse a supplier invoice PDF that has both products and a shipping line
1788
1788
 
1789
1789
  If multiple charges stack (freight + VAT on total incl. freight), put freight first as `Actual`, then VAT second as `On Previous Row Total` referencing the freight row's `idx`.
1790
1790
 
1791
+ ### Adding VAT to a SALES document (quotation / sales order / sales invoice)
1792
+
1793
+ "Add 8.1% VAT / MwSt" needs exactly ONE `taxes[]` row — do it directly with update_document:
1794
+ - `charge_type`: `"On Net Total"`, `rate`: the percentage (e.g. 8.1), `description`: the label (e.g. "MwSt. 8.1%").
1795
+ - `account_head` is OPTIONAL on draft sales documents. If a Tax Payable-like account is already known, use it — but do NOT go hunting for one: if it isn't found in a single lookup, ADD THE ROW WITHOUT `account_head`. Totals still compute (net → tax amount → grand total). NEVER refuse or stall the request because no tax account exists; add the rate-only row and mention the missing account in one sentence.
1796
+ - Then verify with one get_document call and report net / tax amount / gross.
1797
+
1791
1798
  ### Returns (Credit Notes / Debit Notes)
1792
1799
  Returns use the SAME document type with negative quantities and `is_return=1`. Create them by "converting" a document to a return of the same type.
1793
1800
 
@@ -2260,12 +2267,16 @@ async def generate_title(
2260
2267
  # Reasoning loop
2261
2268
  # ---------------------------------------------------------------------------
2262
2269
 
2270
+ # The agentic orchestrator model. Function tools on /v1/chat/completions
2271
+ # require reasoning_effort="none" for the gpt-5.6 family (400 otherwise).
2272
+ ORCHESTRATOR_MODEL = "gpt-5.6-terra"
2273
+
2263
2274
 
2264
2275
  async def run_thinking_loop(
2265
2276
  messages: list[dict],
2266
2277
  on_event,
2267
2278
  session_id: str = None,
2268
- max_iterations: int = 8,
2279
+ max_iterations: int = 12,
2269
2280
  user_info: dict | None = None,
2270
2281
  client_ip: str | None = None,
2271
2282
  ):
@@ -2368,7 +2379,7 @@ async def run_thinking_loop(
2368
2379
  # on ANY exit path — including asyncio.CancelledError raised after
2369
2380
  # the SDK call has already returned but before settle() ran.
2370
2381
  settled = False
2371
- model_name = "gpt-5.6-terra"
2382
+ model_name = ORCHESTRATOR_MODEL
2372
2383
  try:
2373
2384
  print(f"[chat_llm] provider=openai model={model_name} session_id={session_id or '-'} iter={iteration + 1}", flush=True)
2374
2385
  await on_event({"type": "llm_provider", "provider": "openai", "model": model_name})
@@ -2514,7 +2525,36 @@ async def run_thinking_loop(
2514
2525
  ],
2515
2526
  })
2516
2527
 
2528
+ # Step cap reached. The canned "check the results above" is a dead end on the
2529
+ # API channel — the REST surface returns only this final text, so there is no
2530
+ # "above" for a headless orchestrator. Ask the model for ONE tool-free wrap-up
2531
+ # that states concretely what was and wasn't changed, so the caller can react.
2532
+ # Demo visitors keep the canned text (no spend outside the per-iteration
2533
+ # reservations); any failure falls back to the canned text.
2517
2534
  content = "I've reached the maximum number of reasoning steps. Here's what I've done so far — please check the results above."
2535
+ if not demo_mode:
2536
+ try:
2537
+ wrap = await asyncio.to_thread(
2538
+ openai_client.chat.completions.create,
2539
+ model=ORCHESTRATOR_MODEL,
2540
+ messages=messages + [{
2541
+ "role": "user",
2542
+ "content": (
2543
+ "You hit the step limit before finishing. In the user's language, state "
2544
+ "concretely: (1) what you changed (which documents/records, with amounts), "
2545
+ "(2) what you did NOT get done and why, (3) the current state of the affected "
2546
+ "record(s) as far as you know it. Be specific — names and numbers. This text "
2547
+ "stands alone: do NOT refer to 'the results above' or to earlier messages."
2548
+ ),
2549
+ }],
2550
+ max_completion_tokens=600,
2551
+ reasoning_effort="none",
2552
+ )
2553
+ wrap_text = (wrap.choices[0].message.content or "").strip()
2554
+ if wrap_text:
2555
+ content = wrap_text
2556
+ except Exception:
2557
+ pass
2518
2558
  messages.append({"role": "assistant", "content": content})
2519
2559
  await on_event({"type": "complete", "content": content})
2520
2560
 
@@ -58,11 +58,23 @@ def _extract_documents(reply: str, request: Request) -> list[dict]:
58
58
  if (doctype_slug, name) in seen:
59
59
  continue
60
60
  seen.add((doctype_slug, name))
61
- documents.append({
61
+ entry = {
62
62
  "doctype": doctype_slug,
63
63
  "name": name,
64
64
  "pdf_url": f"{base}/api/v1/documents/{doctype_slug}/{name}/pdf",
65
- })
65
+ }
66
+ # Version stamp: a document can change after an earlier turn fetched its
67
+ # PDF, making that attachment stale. `modified` lets the caller tell v1
68
+ # from v2 (e.g. timestamped attachment filenames) and reason about which
69
+ # is current. Best-effort — a vanished doc still gets its pdf_url.
70
+ try:
71
+ doc = load_document(doctype_slug, name)
72
+ modified = doc.get("modified") if isinstance(doc, dict) else None
73
+ if modified:
74
+ entry["modified"] = str(modified)
75
+ except Exception:
76
+ pass
77
+ documents.append(entry)
66
78
  return documents
67
79
 
68
80
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lambda-erp"
3
- version = "0.1.42"
3
+ version = "0.1.43"
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