lambda-erp 0.3.1__tar.gz → 0.3.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.
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/PKG-INFO +1 -1
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/chat.py +16 -3
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/routers/masters.py +8 -1
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/journal_entry.py +28 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/pyproject.toml +1 -1
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/.gitignore +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/LICENSE +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/README.md +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/__init__.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/attachments.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/auth.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/bootstrap.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/demo_limits.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/deps.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/errors.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/main.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/oauth.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/pdf.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/providers.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/remarks_md.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/routers/__init__.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/routers/accounting.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/routers/admin.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/routers/analytics.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/routers/bank_reconciliation.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/routers/chat_api.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/routers/documents.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/routers/proposals.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/routers/reports.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/routers/setup.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/services.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/templates/document.html +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/api/templates/proposal.html +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/docs/agents/README.md +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/frontend/LICENSE +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/frontend/README.md +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/frontend/src/api/client.ts +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/__init__.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/__init__.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/bank_transaction.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/budget.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/chart_of_accounts.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/general_ledger.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/payment_entry.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/pos_invoice.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/purchase_invoice.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/revaluation.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/sales_invoice.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/setup/__init__.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/setup/engine.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/setup/pack.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/setup/packs/__init__.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/setup/packs/ch.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/setup/packs/de_common.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/setup/packs/de_skr03.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/setup/packs/de_skr04.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/setup/packs/generic.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/setup/profiles.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/setup/spine.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/accounting/subscription.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/buying/__init__.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/buying/purchase_order.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/controllers/__init__.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/controllers/currency.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/controllers/defaults.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/controllers/pricing_rule.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/controllers/taxes_and_totals.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/database.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/exceptions.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/hooks.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/model.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/selling/__init__.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/selling/proposal.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/selling/quotation.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/selling/sales_order.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/simulation.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/stock/__init__.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/stock/delivery_note.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/stock/purchase_receipt.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/stock/stock_entry.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/stock/stock_ledger.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/lambda_erp/utils.py +0 -0
- {lambda_erp-0.3.1 → lambda_erp-0.3.3}/terraform/README.md +0 -0
|
@@ -595,6 +595,7 @@ TOOLS = [
|
|
|
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
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."},
|
|
598
|
+
"include_disabled": {"type": "boolean", "description": "Default false (active records only). Set true to ALSO return disabled/archived records — needed to find a record you must inspect, re-enable, or reference, or to answer 'what's disabled'. Retry with this if a lookup for a record you know exists comes back empty."},
|
|
598
599
|
},
|
|
599
600
|
"required": ["master_type"],
|
|
600
601
|
},
|
|
@@ -1326,9 +1327,14 @@ def _handle_search_masters(args):
|
|
|
1326
1327
|
|
|
1327
1328
|
doctype, _name_field = entry
|
|
1328
1329
|
has_disabled = "disabled" in db._get_table_columns(doctype)
|
|
1330
|
+
# Active-only by default (retired/archived records are hidden — you shouldn't
|
|
1331
|
+
# book to them or wade through them). Set include_disabled to reach a disabled
|
|
1332
|
+
# record you need to inspect, re-enable, or reference. Only meaningful when the
|
|
1333
|
+
# table has a `disabled` column.
|
|
1334
|
+
filter_disabled = has_disabled and not bool(args.get("include_disabled"))
|
|
1329
1335
|
|
|
1330
1336
|
if not query:
|
|
1331
|
-
filters = {"disabled": 0} if
|
|
1337
|
+
filters = {"disabled": 0} if filter_disabled else None
|
|
1332
1338
|
return db.get_all(doctype, filters=filters, fields=["*"], limit=limit)
|
|
1333
1339
|
|
|
1334
1340
|
# Optional `fields` narrows the search to specific columns — cheaper, more
|
|
@@ -1343,7 +1349,7 @@ def _handle_search_masters(args):
|
|
|
1343
1349
|
else:
|
|
1344
1350
|
search_cols = _master_search_columns(db, doctype)
|
|
1345
1351
|
|
|
1346
|
-
active_prefix = "disabled = 0 AND " if
|
|
1352
|
+
active_prefix = "disabled = 0 AND " if filter_disabled else ""
|
|
1347
1353
|
|
|
1348
1354
|
# Case-insensitive substring match. lower() on both sides is portable (bare
|
|
1349
1355
|
# LIKE is case-insensitive on SQLite but case-sensitive on Postgres, which
|
|
@@ -1359,7 +1365,7 @@ def _handle_search_masters(args):
|
|
|
1359
1365
|
return [dict(r) for r in rows]
|
|
1360
1366
|
|
|
1361
1367
|
# Nothing matched literally — try fuzzy matching to catch misspellings.
|
|
1362
|
-
return _fuzzy_master_search(db, doctype, search_cols, query,
|
|
1368
|
+
return _fuzzy_master_search(db, doctype, search_cols, query, filter_disabled, limit=limit)
|
|
1363
1369
|
|
|
1364
1370
|
|
|
1365
1371
|
def _ignored_master_fields(master_type: str, data: dict) -> list[str]:
|
|
@@ -1904,6 +1910,13 @@ If `jurisdiction.is_fallback` is true, tell the user their country isn't localiz
|
|
|
1904
1910
|
|
|
1905
1911
|
You help users manage their business by creating documents, looking up data, and running reports — all through natural conversation.
|
|
1906
1912
|
|
|
1913
|
+
## Never fabricate actions or results
|
|
1914
|
+
Only state that something was done — created, changed, enabled/disabled, booked, repointed — **after a tool call returns a success result that confirms it**, and check the returned record actually reflects the change (after `update_master` with {{"disabled": 0}}, confirm the returned row shows `disabled = 0`; after submitting a journal entry, confirm it posted non-zero GL). If a tool returns an error, a warning, or a record that doesn't reflect your intent, tell the user it did **not** work and why — never smooth it over as success.
|
|
1915
|
+
- If a capability doesn't exist, say so plainly. There is **no rename** for accounts or other masters — a master's identifying `name`/number is immutable; you can only edit display fields or create a new record and migrate. Never claim you "renamed" an account.
|
|
1916
|
+
- Never assert a field or capability is missing without checking (e.g. an item's income account) — verify with `get_master_fields` / `search_masters` first.
|
|
1917
|
+
- A master you can't find via `search_masters` may just be **disabled** (search hides disabled by default). Retry with `include_disabled: true` before concluding it doesn't exist — you need this to re-enable a disabled account.
|
|
1918
|
+
- If you can't confirm an action took effect, re-fetch and look, or tell the user you couldn't confirm it — never guess.
|
|
1919
|
+
|
|
1907
1920
|
## Answering data questions — three paths
|
|
1908
1921
|
|
|
1909
1922
|
**Path 1: single-record lookup → `list_documents` / `get_document`.** For "is SINV-0042 paid", "what did customer X order last", "show me the latest 5 purchase orders" — fetch the rows directly. Don't try to aggregate in your head unless there are only a handful of rows in front of you.
|
|
@@ -187,7 +187,14 @@ def _generate_master_name(db, doctype: str, prefix: str) -> str:
|
|
|
187
187
|
def _normalize_master_data(data: dict) -> dict:
|
|
188
188
|
normalized = _dict(data)
|
|
189
189
|
for key, value in list(normalized.items()):
|
|
190
|
-
|
|
190
|
+
# Flag columns (disabled, is_group, …) are INTEGER, not boolean. The chat
|
|
191
|
+
# may send a JSON bool; coerce true/false -> 1/0 so e.g. re-enabling an
|
|
192
|
+
# account with {"disabled": false} actually persists (on Postgres a bool
|
|
193
|
+
# into an INTEGER column errors, which then gets narrated as "done"). Check
|
|
194
|
+
# bool BEFORE int — in Python bool is a subclass of int.
|
|
195
|
+
if isinstance(value, bool):
|
|
196
|
+
normalized[key] = int(value)
|
|
197
|
+
elif isinstance(value, str) and value.strip() == "":
|
|
191
198
|
normalized[key] = None
|
|
192
199
|
return normalized
|
|
193
200
|
|
|
@@ -70,10 +70,33 @@ class JournalEntry(Document):
|
|
|
70
70
|
if not self.posting_date:
|
|
71
71
|
self.posting_date = nowdate()
|
|
72
72
|
|
|
73
|
+
self._normalize_amounts()
|
|
73
74
|
self._validate_debit_credit()
|
|
74
75
|
self._validate_references()
|
|
75
76
|
self._set_totals()
|
|
76
77
|
|
|
78
|
+
def _normalize_amounts(self):
|
|
79
|
+
"""Keep each row's base debit/credit and its *_in_account_currency twin in
|
|
80
|
+
sync. lambda-erp has no per-line exchange rate (base == account currency),
|
|
81
|
+
so back-fill whichever side is missing. Without this, a row created with
|
|
82
|
+
the amount ONLY in debit_in_account_currency (as the chat's create path
|
|
83
|
+
did) leaves base debit/credit at 0 — it passes the 0==0 balance check,
|
|
84
|
+
submits "successfully", and posts NOTHING (GL is built from the base
|
|
85
|
+
fields, and _get_gl_entries skips 0/0 rows). Idempotent."""
|
|
86
|
+
for row in self.get("accounts") or []:
|
|
87
|
+
debit = flt(row.get("debit"))
|
|
88
|
+
credit = flt(row.get("credit"))
|
|
89
|
+
dr_ac = flt(row.get("debit_in_account_currency"))
|
|
90
|
+
cr_ac = flt(row.get("credit_in_account_currency"))
|
|
91
|
+
if not debit and dr_ac:
|
|
92
|
+
row["debit"] = dr_ac
|
|
93
|
+
if not credit and cr_ac:
|
|
94
|
+
row["credit"] = cr_ac
|
|
95
|
+
if not dr_ac and debit:
|
|
96
|
+
row["debit_in_account_currency"] = debit
|
|
97
|
+
if not cr_ac and credit:
|
|
98
|
+
row["credit_in_account_currency"] = credit
|
|
99
|
+
|
|
77
100
|
def _validate_debit_credit(self):
|
|
78
101
|
"""Ensure total debits == total credits.
|
|
79
102
|
|
|
@@ -100,6 +123,11 @@ class JournalEntry(Document):
|
|
|
100
123
|
def on_submit(self):
|
|
101
124
|
"""Post GL entries directly from account rows."""
|
|
102
125
|
gl_entries = self._get_gl_entries()
|
|
126
|
+
if not gl_entries:
|
|
127
|
+
raise ValidationError(
|
|
128
|
+
"Journal Entry has no non-zero lines to post — every account row is "
|
|
129
|
+
"0/0. Set a debit or credit amount before submitting."
|
|
130
|
+
)
|
|
103
131
|
make_gl_entries(gl_entries)
|
|
104
132
|
self._update_referenced_outstanding()
|
|
105
133
|
|
|
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
|