lambda-erp 0.1.41__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.
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/PKG-INFO +1 -1
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/chat.py +42 -2
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/providers.py +4 -4
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/routers/chat_api.py +14 -2
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/pyproject.toml +1 -1
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/.gitignore +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/LICENSE +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/README.md +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/__init__.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/attachments.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/auth.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/bootstrap.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/demo_limits.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/deps.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/errors.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/main.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/oauth.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/pdf.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/remarks_md.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/routers/__init__.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/routers/accounting.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/routers/admin.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/routers/analytics.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/routers/bank_reconciliation.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/routers/documents.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/routers/masters.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/routers/proposals.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/routers/reports.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/routers/setup.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/services.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/templates/document.html +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/api/templates/proposal.html +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/docs/agents/README.md +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/frontend/README.md +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/frontend/src/api/client.ts +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/__init__.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/accounting/__init__.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/accounting/bank_transaction.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/accounting/budget.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/accounting/chart_of_accounts.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/accounting/general_ledger.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/accounting/journal_entry.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/accounting/payment_entry.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/accounting/pos_invoice.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/accounting/purchase_invoice.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/accounting/revaluation.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/accounting/sales_invoice.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/accounting/subscription.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/buying/__init__.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/buying/purchase_order.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/controllers/__init__.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/controllers/currency.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/controllers/defaults.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/controllers/pricing_rule.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/controllers/taxes_and_totals.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/database.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/exceptions.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/hooks.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/model.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/selling/__init__.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/selling/proposal.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/selling/quotation.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/selling/sales_order.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/simulation.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/stock/__init__.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/stock/delivery_note.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/stock/purchase_receipt.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/stock/stock_entry.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/stock/stock_ledger.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/lambda_erp/utils.py +0 -0
- {lambda_erp-0.1.41 → lambda_erp-0.1.43}/terraform/README.md +0 -0
|
@@ -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 =
|
|
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 =
|
|
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
|
|
|
@@ -25,13 +25,13 @@ OPENAI_PRICING: dict[str, dict[str, Any]] = {
|
|
|
25
25
|
{"max_input_tokens": None, "input": 5.00, "cached_input": 0.50, "output": 22.50},
|
|
26
26
|
],
|
|
27
27
|
},
|
|
28
|
-
# GPT-5.6 Terra — tiered like gpt-5.4 (official
|
|
29
|
-
# 272K input
|
|
28
|
+
# GPT-5.6 Terra — tiered like gpt-5.4 (official pricing: 2x input, 1.5x
|
|
29
|
+
# output above 272K input; the 5.6 family also prices cache writes).
|
|
30
30
|
"gpt-5.6-terra": {
|
|
31
31
|
"tiered": True,
|
|
32
32
|
"tiers": [
|
|
33
|
-
{"max_input_tokens": 272_000, "input": 2.50, "cached_input": 0.25, "output": 15.00},
|
|
34
|
-
{"max_input_tokens": None, "input": 5.00, "cached_input": 0.50, "output": 22.50},
|
|
33
|
+
{"max_input_tokens": 272_000, "input": 2.50, "cached_input": 0.25, "cache_write": 3.125, "output": 15.00},
|
|
34
|
+
{"max_input_tokens": None, "input": 5.00, "cached_input": 0.50, "cache_write": 6.25, "output": 22.50},
|
|
35
35
|
],
|
|
36
36
|
},
|
|
37
37
|
"gpt-4.1-nano": {"input": 0.10, "cached_input": 0.025, "output": 0.40},
|
|
@@ -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
|
-
|
|
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
|
|
|
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
|