lambda-erp 0.1.44__tar.gz → 0.1.46__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.44 → lambda_erp-0.1.46}/PKG-INFO +1 -1
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/chat.py +58 -3
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/routers/masters.py +16 -3
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/routers/reports.py +106 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/frontend/src/api/client.ts +5 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/pyproject.toml +1 -1
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/.gitignore +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/LICENSE +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/README.md +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/__init__.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/attachments.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/auth.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/bootstrap.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/demo_limits.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/deps.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/errors.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/main.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/oauth.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/pdf.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/providers.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/remarks_md.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/routers/__init__.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/routers/accounting.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/routers/admin.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/routers/analytics.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/routers/bank_reconciliation.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/routers/chat_api.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/routers/documents.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/routers/proposals.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/routers/setup.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/services.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/templates/document.html +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/api/templates/proposal.html +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/docs/agents/README.md +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/frontend/README.md +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/__init__.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/accounting/__init__.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/accounting/bank_transaction.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/accounting/budget.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/accounting/chart_of_accounts.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/accounting/general_ledger.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/accounting/journal_entry.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/accounting/payment_entry.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/accounting/pos_invoice.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/accounting/purchase_invoice.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/accounting/revaluation.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/accounting/sales_invoice.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/accounting/subscription.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/buying/__init__.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/buying/purchase_order.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/controllers/__init__.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/controllers/currency.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/controllers/defaults.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/controllers/pricing_rule.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/controllers/taxes_and_totals.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/database.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/exceptions.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/hooks.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/model.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/selling/__init__.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/selling/proposal.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/selling/quotation.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/selling/sales_order.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/simulation.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/stock/__init__.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/stock/delivery_note.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/stock/purchase_receipt.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/stock/stock_entry.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/stock/stock_ledger.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/lambda_erp/utils.py +0 -0
- {lambda_erp-0.1.44 → lambda_erp-0.1.46}/terraform/README.md +0 -0
|
@@ -34,7 +34,7 @@ from api.demo_limits import (
|
|
|
34
34
|
limiter as demo_limiter,
|
|
35
35
|
)
|
|
36
36
|
from api.providers import cost_of_anthropic_call, cost_of_openai_call, cost_of_transcription
|
|
37
|
-
from api.routers.masters import create_master_record, update_master_record, MASTER_IDENTITY_ALIAS
|
|
37
|
+
from api.routers.masters import create_master_record, update_master_record, delete_master_record, MASTER_IDENTITY_ALIAS
|
|
38
38
|
from lambda_erp.database import get_db
|
|
39
39
|
from lambda_erp.utils import flt, now, nowdate
|
|
40
40
|
|
|
@@ -661,6 +661,29 @@ TOOLS = [
|
|
|
661
661
|
},
|
|
662
662
|
},
|
|
663
663
|
},
|
|
664
|
+
{
|
|
665
|
+
"type": "function",
|
|
666
|
+
"function": {
|
|
667
|
+
"name": "delete_master",
|
|
668
|
+
"description": (
|
|
669
|
+
"Delete a master record (customer, supplier, item, ...). ADMIN role only. "
|
|
670
|
+
"Reference-protected and safe by design: an UNREFERENCED record is permanently deleted; "
|
|
671
|
+
"a record referenced anywhere (documents, GL, stock, ...) is NOT deleted — it is automatically "
|
|
672
|
+
"disabled/archived instead and the result names the blocking reference (status='disabled'). "
|
|
673
|
+
"Use when the user explicitly asks to delete/remove a master record (e.g. a mistakenly created "
|
|
674
|
+
"or duplicate one). If they merely want it out of the way, prefer update_master with "
|
|
675
|
+
"{\"disabled\": 1}. Example: {\"master_type\":\"customer\",\"name\":\"CUST-007\"}"
|
|
676
|
+
),
|
|
677
|
+
"parameters": {
|
|
678
|
+
"type": "object",
|
|
679
|
+
"properties": {
|
|
680
|
+
"master_type": {"type": "string", "enum": MASTER_TYPES},
|
|
681
|
+
"name": {"type": "string", "description": "Existing master record ID/name to delete."},
|
|
682
|
+
},
|
|
683
|
+
"required": ["master_type", "name"],
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
},
|
|
664
687
|
{
|
|
665
688
|
"type": "function",
|
|
666
689
|
"function": {
|
|
@@ -1304,6 +1327,31 @@ def _handle_update_master(args):
|
|
|
1304
1327
|
return result
|
|
1305
1328
|
|
|
1306
1329
|
|
|
1330
|
+
def _handle_delete_master(args, user_info=None):
|
|
1331
|
+
"""Reference-protected master deletion, gated to the admin role.
|
|
1332
|
+
|
|
1333
|
+
Registered per-run (run_thinking_loop scopes it with the caller's
|
|
1334
|
+
user_info) — NOT in the base TOOL_HANDLERS dict, whose handlers are
|
|
1335
|
+
called with (args) only.
|
|
1336
|
+
"""
|
|
1337
|
+
role = (user_info or {}).get("role")
|
|
1338
|
+
if role != "admin":
|
|
1339
|
+
return {
|
|
1340
|
+
"error": (
|
|
1341
|
+
"Deleting master records requires the admin role. Ask an ERP admin to do it, "
|
|
1342
|
+
"or disable the record instead: update_master with {\"disabled\": 1}."
|
|
1343
|
+
)
|
|
1344
|
+
}
|
|
1345
|
+
name = args.get("name")
|
|
1346
|
+
if not name:
|
|
1347
|
+
return {"error": "You must pass the existing master record 'name' to delete. Example: {\"master_type\":\"customer\",\"name\":\"CUST-007\"}"}
|
|
1348
|
+
try:
|
|
1349
|
+
return dict(delete_master_record(args["master_type"], name))
|
|
1350
|
+
except Exception as exc:
|
|
1351
|
+
detail = getattr(exc, "detail", None)
|
|
1352
|
+
return {"error": detail or str(exc)}
|
|
1353
|
+
|
|
1354
|
+
|
|
1307
1355
|
def _handle_get_current_time(args):
|
|
1308
1356
|
from datetime import datetime, timezone
|
|
1309
1357
|
now = datetime.now(timezone.utc)
|
|
@@ -1855,7 +1903,7 @@ For purchased goods, prefer Purchase Receipt when receiving separately from bill
|
|
|
1855
1903
|
- **Draft (docstatus=0):** Document is editable. Has NO financial impact — no GL entries, no stock movement. Can be updated freely.
|
|
1856
1904
|
- **Submitted (docstatus=1):** Document is locked. GL entries and stock ledger entries are posted. Cannot be edited — only cancelled.
|
|
1857
1905
|
- **Cancelled (docstatus=2):** GL and stock entries are reversed. Document is permanently archived. Cannot be reused or edited.
|
|
1858
|
-
- **
|
|
1906
|
+
- **DOCUMENTS have NO delete operation.** Documents are permanent records — by design for audit integrity. This rule applies to DOCUMENTS ONLY; never claim master records can't be deleted "for audit integrity" (see Master Data Deletion below).
|
|
1859
1907
|
- **To void a draft you no longer need:** submit it first (docstatus 0→1), then cancel it (docstatus 1→2). Do NOT tell the user to "delete" anything.
|
|
1860
1908
|
- **To correct a submitted document:** cancel it and create a new one with the correct values.
|
|
1861
1909
|
- Only submitted documents can be converted (e.g. Sales Order → Sales Invoice). Draft or cancelled documents cannot.
|
|
@@ -1864,6 +1912,9 @@ For purchased goods, prefer Purchase Receipt when receiving separately from bill
|
|
|
1864
1912
|
## Master Data Types
|
|
1865
1913
|
customer, supplier, item, warehouse, account, company, cost-center
|
|
1866
1914
|
|
|
1915
|
+
## Master Data Deletion
|
|
1916
|
+
Master records CAN be deleted — use the `delete_master` tool (ADMIN role only). It is reference-protected: an unreferenced record (e.g. a mistakenly created or duplicate one) is permanently deleted; a record referenced by any document, GL entry, or stock data is NOT deleted but automatically disabled/archived instead, and the result names the blocking reference — relay that reason to the user. When the user merely wants a record out of the way (not destroyed), prefer `update_master` with `{"disabled": 1}`. Non-admin users: explain that deletion needs an admin and offer to disable instead.
|
|
1917
|
+
|
|
1867
1918
|
## Warehouse Master Rules
|
|
1868
1919
|
- To create a warehouse via `create_master`, you MUST pass a `data` object.
|
|
1869
1920
|
- `warehouse_name` is required.
|
|
@@ -2325,6 +2376,9 @@ async def run_thinking_loop(
|
|
|
2325
2376
|
)
|
|
2326
2377
|
|
|
2327
2378
|
tool_handlers = dict(TOOL_HANDLERS)
|
|
2379
|
+
# delete_master needs the caller's role (admin-only) — scoped here rather
|
|
2380
|
+
# than in TOOL_HANDLERS, whose handlers are called with (args) only.
|
|
2381
|
+
tool_handlers["delete_master"] = lambda args: _handle_delete_master(args, user_info)
|
|
2328
2382
|
|
|
2329
2383
|
user_role = user_info.get("role") if user_info else None
|
|
2330
2384
|
demo_mode = is_demo_role(user_role)
|
|
@@ -2333,9 +2387,10 @@ async def run_thinking_loop(
|
|
|
2333
2387
|
max_completion = demo_max_completion_tokens() if demo_mode else 4096
|
|
2334
2388
|
|
|
2335
2389
|
if demo_mode:
|
|
2336
|
-
denied = lambda _args: {"error": "Demo mode cannot create or
|
|
2390
|
+
denied = lambda _args: {"error": "Demo mode cannot create, update, or delete master data."}
|
|
2337
2391
|
tool_handlers["create_master"] = denied
|
|
2338
2392
|
tool_handlers["update_master"] = denied
|
|
2393
|
+
tool_handlers["delete_master"] = denied
|
|
2339
2394
|
|
|
2340
2395
|
# Scope session-aware tools without mutating globals.
|
|
2341
2396
|
if session_id:
|
|
@@ -353,11 +353,19 @@ def update_master(master_type: str, name: str, data: dict, _user: dict = _manage
|
|
|
353
353
|
return update_master_record(master_type, name, data)
|
|
354
354
|
|
|
355
355
|
|
|
356
|
-
|
|
357
|
-
|
|
356
|
+
def delete_master_record(master_type: str, name: str) -> dict:
|
|
357
|
+
"""Delete a master record with reference protection — the ONLY sanctioned
|
|
358
|
+
delete path (the tables have no FK constraints, so every caller — the REST
|
|
359
|
+
route below AND the chat agent's delete_master tool — must go through here).
|
|
360
|
+
|
|
361
|
+
Unreferenced record → hard delete. Referenced record → auto-disabled instead
|
|
362
|
+
(when the doctype has a `disabled` column) with the blocking reference named,
|
|
363
|
+
else a 409. Raising HTTPException keeps route behavior; the chat handler
|
|
364
|
+
catches it and surfaces `detail` as the tool error.
|
|
365
|
+
"""
|
|
358
366
|
doctype, _ = _get_table(master_type)
|
|
359
367
|
if not doctype:
|
|
360
|
-
|
|
368
|
+
raise HTTPException(status_code=422, detail=f"Unknown master type: {master_type}")
|
|
361
369
|
db = get_db()
|
|
362
370
|
if not db.exists(doctype, name):
|
|
363
371
|
raise HTTPException(status_code=404, detail=f"{doctype} {name} not found")
|
|
@@ -374,6 +382,11 @@ def delete_master(master_type: str, name: str, _user: dict = _admin):
|
|
|
374
382
|
return {"ok": True, "status": "deleted"}
|
|
375
383
|
|
|
376
384
|
|
|
385
|
+
@router.delete("/{master_type}/{name}")
|
|
386
|
+
def delete_master(master_type: str, name: str, _user: dict = _admin):
|
|
387
|
+
return delete_master_record(master_type, name)
|
|
388
|
+
|
|
389
|
+
|
|
377
390
|
@router.get("/account/tree")
|
|
378
391
|
def account_tree(company: str | None = None, _user: dict = _viewer):
|
|
379
392
|
"""Return Chart of Accounts as a nested tree."""
|
|
@@ -121,6 +121,112 @@ def trial_balance(
|
|
|
121
121
|
company, presentation_currency, to_date)
|
|
122
122
|
|
|
123
123
|
|
|
124
|
+
def _chart_of_accounts(db, company=None, from_date=None, to_date=None):
|
|
125
|
+
"""Chart of accounts (every account, incl. groups and zero-balance ones)
|
|
126
|
+
with period balances — the tree the GUI renders.
|
|
127
|
+
|
|
128
|
+
Balance semantics follow standard presentation:
|
|
129
|
+
- Balance Sheet accounts: CLOSING balance as of ``to_date`` (all postings
|
|
130
|
+
up to and including it; ``from_date`` is ignored for them).
|
|
131
|
+
- P&L accounts: movement WITHIN [from_date, to_date].
|
|
132
|
+
Values are natural-signed per root_type (Asset/Expense: debit − credit;
|
|
133
|
+
Liability/Equity/Income: credit − debit) so revenue reads positive.
|
|
134
|
+
Group accounts roll up every descendant's balance (plus any postings of
|
|
135
|
+
their own, though entries normally land on leaves).
|
|
136
|
+
"""
|
|
137
|
+
acc_filters = {}
|
|
138
|
+
if company:
|
|
139
|
+
acc_filters["company"] = company
|
|
140
|
+
accounts = db.get_all(
|
|
141
|
+
"Account", filters=acc_filters,
|
|
142
|
+
fields=["name", "account_name", "parent_account", "root_type",
|
|
143
|
+
"report_type", "account_type", "is_group", "disabled"],
|
|
144
|
+
order_by="name",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def _movement(where_extra, params_extra):
|
|
148
|
+
clause = "is_cancelled = 0" + where_extra
|
|
149
|
+
params = list(params_extra)
|
|
150
|
+
if company:
|
|
151
|
+
clause += " AND company = ?"
|
|
152
|
+
params.append(company)
|
|
153
|
+
found = db.sql(
|
|
154
|
+
f"""SELECT account,
|
|
155
|
+
COALESCE(SUM(debit), 0) AS d,
|
|
156
|
+
COALESCE(SUM(credit), 0) AS c
|
|
157
|
+
FROM "GL Entry" WHERE {clause} GROUP BY account""",
|
|
158
|
+
params,
|
|
159
|
+
)
|
|
160
|
+
return {r["account"]: (flt(r["d"], 2), flt(r["c"], 2)) for r in found}
|
|
161
|
+
|
|
162
|
+
bs_where, bs_params = "", []
|
|
163
|
+
if to_date:
|
|
164
|
+
bs_where, bs_params = " AND posting_date <= ?", [to_date]
|
|
165
|
+
bs_map = _movement(bs_where, bs_params)
|
|
166
|
+
|
|
167
|
+
if from_date or to_date:
|
|
168
|
+
pl_where, pl_params = "", []
|
|
169
|
+
if from_date:
|
|
170
|
+
pl_where += " AND posting_date >= ?"
|
|
171
|
+
pl_params.append(from_date)
|
|
172
|
+
if to_date:
|
|
173
|
+
pl_where += " AND posting_date <= ?"
|
|
174
|
+
pl_params.append(to_date)
|
|
175
|
+
pl_map = _movement(pl_where, pl_params)
|
|
176
|
+
else:
|
|
177
|
+
pl_map = bs_map
|
|
178
|
+
|
|
179
|
+
rows = []
|
|
180
|
+
by_name = {}
|
|
181
|
+
for acc in accounts:
|
|
182
|
+
is_pl = acc.get("report_type") == "Profit and Loss"
|
|
183
|
+
d, c = (pl_map if is_pl else bs_map).get(acc["name"], (0.0, 0.0))
|
|
184
|
+
if acc.get("root_type") in ("Liability", "Equity", "Income"):
|
|
185
|
+
natural = c - d
|
|
186
|
+
else:
|
|
187
|
+
natural = d - c
|
|
188
|
+
row = {
|
|
189
|
+
"name": acc["name"],
|
|
190
|
+
"account_name": acc.get("account_name"),
|
|
191
|
+
"parent_account": acc.get("parent_account") or None,
|
|
192
|
+
"root_type": acc.get("root_type"),
|
|
193
|
+
"account_type": acc.get("account_type"),
|
|
194
|
+
"is_group": bool(acc.get("is_group")),
|
|
195
|
+
"disabled": bool(acc.get("disabled")),
|
|
196
|
+
"balance": flt(natural, 2),
|
|
197
|
+
"has_entries": bool(d or c),
|
|
198
|
+
}
|
|
199
|
+
rows.append(row)
|
|
200
|
+
by_name[row["name"]] = row
|
|
201
|
+
|
|
202
|
+
# Roll every account's own balance up its ancestor chain so group rows
|
|
203
|
+
# show subtree totals.
|
|
204
|
+
subtree_extra = {r["name"]: 0.0 for r in rows}
|
|
205
|
+
for row in rows:
|
|
206
|
+
parent = row["parent_account"]
|
|
207
|
+
while parent and parent in by_name:
|
|
208
|
+
subtree_extra[parent] += row["balance"]
|
|
209
|
+
parent = by_name[parent]["parent_account"]
|
|
210
|
+
for row in rows:
|
|
211
|
+
if row["is_group"]:
|
|
212
|
+
row["balance"] = flt(row["balance"] + subtree_extra[row["name"]], 2)
|
|
213
|
+
row["has_entries"] = row["has_entries"] or bool(subtree_extra[row["name"]])
|
|
214
|
+
|
|
215
|
+
return {"accounts": rows, "from_date": from_date, "to_date": to_date}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@router.get("/chart-of-accounts")
|
|
219
|
+
def chart_of_accounts(
|
|
220
|
+
company: str | None = None,
|
|
221
|
+
from_date: str | None = None,
|
|
222
|
+
to_date: str | None = None,
|
|
223
|
+
presentation_currency: str | None = None,
|
|
224
|
+
):
|
|
225
|
+
db = get_db()
|
|
226
|
+
return _present(db, _chart_of_accounts(db, company, from_date, to_date),
|
|
227
|
+
company, presentation_currency, to_date)
|
|
228
|
+
|
|
229
|
+
|
|
124
230
|
def _general_ledger(db, filters=None):
|
|
125
231
|
filters = filters or {}
|
|
126
232
|
where = ["is_cancelled = 0"]
|
|
@@ -226,6 +226,11 @@ export const api = {
|
|
|
226
226
|
`/reports/trial-balance${qs(params)}`,
|
|
227
227
|
),
|
|
228
228
|
|
|
229
|
+
chartOfAccounts: (params?: Record<string, string>) =>
|
|
230
|
+
request<{ accounts: any[]; from_date: string | null; to_date: string | null }>(
|
|
231
|
+
`/reports/chart-of-accounts${qs(params)}`,
|
|
232
|
+
),
|
|
233
|
+
|
|
229
234
|
generalLedger: (params?: Record<string, string>) =>
|
|
230
235
|
request<{
|
|
231
236
|
rows: any[];
|
|
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
|