lambda-erp 0.6.5__tar.gz → 0.6.7__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.6.5 → lambda_erp-0.6.7}/PKG-INFO +1 -1
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/documents.py +13 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/services.py +23 -4
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/utils.py +10 -3
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/pyproject.toml +1 -1
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/.gitignore +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/LICENSE +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/README.md +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/__init__.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/attachments.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/auth.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/bootstrap.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/chat.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/demo_limits.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/deps.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/errors.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/main.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/oauth.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/pdf.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/providers.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/remarks_md.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/__init__.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/accounting.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/admin.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/analytics.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/bank_reconciliation.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/chat_api.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/masters.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/proposals.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/reports.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/routers/setup.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/templates/document.html +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/api/templates/proposal.html +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/docs/agents/README.md +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/frontend/LICENSE +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/frontend/README.md +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/frontend/src/api/client.ts +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/__init__.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/__init__.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/bank_transaction.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/budget.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/chart_of_accounts.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/general_ledger.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/journal_entry.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/payment_entry.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/pos_invoice.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/purchase_invoice.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/revaluation.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/sales_invoice.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/__init__.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/engine.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/pack.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/packs/__init__.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/packs/ch.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/packs/de_common.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/packs/de_skr03.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/packs/de_skr04.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/packs/generic.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/profiles.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/setup/spine.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/accounting/subscription.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/buying/__init__.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/buying/purchase_order.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/controllers/__init__.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/controllers/currency.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/controllers/defaults.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/controllers/pricing_rule.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/controllers/taxes_and_totals.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/database.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/exceptions.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/hooks.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/model.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/selling/__init__.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/selling/proposal.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/selling/quotation.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/selling/sales_order.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/simulation.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/stock/__init__.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/stock/delivery_note.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/stock/purchase_receipt.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/stock/stock_entry.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/lambda_erp/stock/stock_ledger.py +0 -0
- {lambda_erp-0.6.5 → lambda_erp-0.6.7}/terraform/README.md +0 -0
|
@@ -28,6 +28,7 @@ _manager = Depends(require_role("manager"))
|
|
|
28
28
|
_LIST_RESERVED = {
|
|
29
29
|
"status", "party", "from_date", "to_date", "docstatus",
|
|
30
30
|
"include_discarded", "limit", "offset", "order_by", "order",
|
|
31
|
+
"date_field",
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
|
|
@@ -43,6 +44,7 @@ def list_docs(
|
|
|
43
44
|
include_discarded: bool = False,
|
|
44
45
|
order_by: str | None = None,
|
|
45
46
|
order: str = "desc",
|
|
47
|
+
date_field: str | None = None,
|
|
46
48
|
limit: int = Query(default=50, le=500),
|
|
47
49
|
offset: int = Query(default=0, ge=0),
|
|
48
50
|
_user: dict = _viewer,
|
|
@@ -70,6 +72,14 @@ def list_docs(
|
|
|
70
72
|
raise HTTPException(status_code=400, detail=f"Unknown filter field: {key}")
|
|
71
73
|
filters[key] = value
|
|
72
74
|
|
|
75
|
+
# Which column from_date/to_date filter on. The frontend passes its declared
|
|
76
|
+
# dateField so plugin doctypes get working date filters without a server-side
|
|
77
|
+
# map. Passed through untouched here; list_documents ignores it unless it's a
|
|
78
|
+
# real column (so a config with a synthetic dateField degrades to no date
|
|
79
|
+
# filter rather than 400-ing the whole list), and never interpolates it blind.
|
|
80
|
+
if date_field:
|
|
81
|
+
filters["date_field"] = date_field
|
|
82
|
+
|
|
73
83
|
if order_by is not None and order_by not in columns:
|
|
74
84
|
raise HTTPException(status_code=400, detail=f"Unknown order_by field: {order_by}")
|
|
75
85
|
if order.lower() not in ("asc", "desc"):
|
|
@@ -94,6 +104,7 @@ def adjacent_doc(
|
|
|
94
104
|
include_discarded: bool = False,
|
|
95
105
|
order_by: str | None = None,
|
|
96
106
|
order: str = "desc",
|
|
107
|
+
date_field: str | None = None,
|
|
97
108
|
_user: dict = _viewer,
|
|
98
109
|
):
|
|
99
110
|
"""Prev/next record around `name` in the same order+filters the list uses.
|
|
@@ -117,6 +128,8 @@ def adjacent_doc(
|
|
|
117
128
|
if key not in columns:
|
|
118
129
|
raise HTTPException(status_code=400, detail=f"Unknown filter field: {key}")
|
|
119
130
|
filters[key] = value
|
|
131
|
+
if date_field:
|
|
132
|
+
filters["date_field"] = date_field
|
|
120
133
|
if order_by is not None and order_by not in columns:
|
|
121
134
|
raise HTTPException(status_code=400, detail=f"Unknown order_by field: {order_by}")
|
|
122
135
|
if order.lower() not in ("asc", "desc"):
|
|
@@ -392,6 +392,17 @@ DATE_FIELDS = {
|
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
|
|
395
|
+
def _resolve_date_field(db, doctype: str, override: str | None) -> str | None:
|
|
396
|
+
"""The column that from_date/to_date range-filter against. An explicit
|
|
397
|
+
override (the frontend's declared `dateField`, passed as the `date_field`
|
|
398
|
+
filter) wins when it names a real column; otherwise fall back to the
|
|
399
|
+
built-in DATE_FIELDS map for core doctypes. Guarded so the override is never
|
|
400
|
+
interpolated into SQL unless it's an actual column."""
|
|
401
|
+
if override and override in db._get_table_columns(doctype):
|
|
402
|
+
return override
|
|
403
|
+
return DATE_FIELDS.get(doctype)
|
|
404
|
+
|
|
405
|
+
|
|
395
406
|
def _exclude_discarded(db, doctype: str, db_filters: dict, include_discarded: bool) -> None:
|
|
396
407
|
"""Hide voided drafts (discarded=1) unless explicitly requested. Guarded on
|
|
397
408
|
the column existing so non-submittable doctypes are unaffected."""
|
|
@@ -421,6 +432,7 @@ def list_documents(doctype_slug: str, filters: dict = None, limit: int = 50, off
|
|
|
421
432
|
db_filters = {}
|
|
422
433
|
from_date = None
|
|
423
434
|
to_date = None
|
|
435
|
+
date_field_override = None
|
|
424
436
|
if filters:
|
|
425
437
|
for key, value in filters.items():
|
|
426
438
|
if value is None or value == "":
|
|
@@ -429,13 +441,15 @@ def list_documents(doctype_slug: str, filters: dict = None, limit: int = 50, off
|
|
|
429
441
|
from_date = value
|
|
430
442
|
elif key == "to_date":
|
|
431
443
|
to_date = value
|
|
444
|
+
elif key == "date_field":
|
|
445
|
+
date_field_override = value
|
|
432
446
|
else:
|
|
433
447
|
db_filters[key] = value
|
|
434
448
|
|
|
435
449
|
_exclude_discarded(db, doctype, db_filters, include_discarded)
|
|
436
450
|
|
|
437
451
|
# Date range filtering via the doctype's primary date field
|
|
438
|
-
date_field =
|
|
452
|
+
date_field = _resolve_date_field(db, doctype, date_field_override)
|
|
439
453
|
if date_field:
|
|
440
454
|
if from_date:
|
|
441
455
|
db_filters[date_field] = (">=", from_date)
|
|
@@ -481,6 +495,7 @@ def count_documents(doctype_slug: str, filters: dict = None, include_discarded:
|
|
|
481
495
|
db_filters = {}
|
|
482
496
|
from_date = None
|
|
483
497
|
to_date = None
|
|
498
|
+
date_field_override = None
|
|
484
499
|
if filters:
|
|
485
500
|
for key, value in filters.items():
|
|
486
501
|
if value is None or value == "":
|
|
@@ -489,12 +504,14 @@ def count_documents(doctype_slug: str, filters: dict = None, include_discarded:
|
|
|
489
504
|
from_date = value
|
|
490
505
|
elif key == "to_date":
|
|
491
506
|
to_date = value
|
|
507
|
+
elif key == "date_field":
|
|
508
|
+
date_field_override = value
|
|
492
509
|
else:
|
|
493
510
|
db_filters[key] = value
|
|
494
511
|
|
|
495
512
|
_exclude_discarded(db, doctype, db_filters, include_discarded)
|
|
496
513
|
|
|
497
|
-
date_field =
|
|
514
|
+
date_field = _resolve_date_field(db, doctype, date_field_override)
|
|
498
515
|
where_parts = []
|
|
499
516
|
params = []
|
|
500
517
|
if date_field and from_date:
|
|
@@ -524,7 +541,7 @@ def _filter_where(db, doctype: str, filters: dict, include_discarded: bool):
|
|
|
524
541
|
(equality filters + from_date/to_date on the doctype's date field + discard
|
|
525
542
|
exclusion). Same semantics as list_documents/count_documents so prev/next
|
|
526
543
|
matches exactly what the list shows."""
|
|
527
|
-
db_filters, from_date, to_date = {}, None, None
|
|
544
|
+
db_filters, from_date, to_date, date_field_override = {}, None, None, None
|
|
528
545
|
for key, value in (filters or {}).items():
|
|
529
546
|
if value is None or value == "":
|
|
530
547
|
continue
|
|
@@ -532,11 +549,13 @@ def _filter_where(db, doctype: str, filters: dict, include_discarded: bool):
|
|
|
532
549
|
from_date = value
|
|
533
550
|
elif key == "to_date":
|
|
534
551
|
to_date = value
|
|
552
|
+
elif key == "date_field":
|
|
553
|
+
date_field_override = value
|
|
535
554
|
else:
|
|
536
555
|
db_filters[key] = value
|
|
537
556
|
_exclude_discarded(db, doctype, db_filters, include_discarded)
|
|
538
557
|
|
|
539
|
-
date_field =
|
|
558
|
+
date_field = _resolve_date_field(db, doctype, date_field_override)
|
|
540
559
|
where_parts, params = [], []
|
|
541
560
|
if date_field and from_date:
|
|
542
561
|
where_parts.append(f'"{date_field}" >= ?'); params.append(from_date)
|
|
@@ -6,7 +6,7 @@ throughout the the reference implementation business logic.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import math
|
|
9
|
-
from datetime import date, datetime, timedelta
|
|
9
|
+
from datetime import date, datetime, timedelta, timezone
|
|
10
10
|
from decimal import Decimal, ROUND_HALF_UP
|
|
11
11
|
|
|
12
12
|
def flt(value, precision=None):
|
|
@@ -54,8 +54,15 @@ def nowdate():
|
|
|
54
54
|
return date.today().isoformat()
|
|
55
55
|
|
|
56
56
|
def now():
|
|
57
|
-
"""Return current
|
|
58
|
-
|
|
57
|
+
"""Return the current instant as a timezone-aware UTC ISO string
|
|
58
|
+
(e.g. "2026-07-29T09:57:00.123456+00:00").
|
|
59
|
+
|
|
60
|
+
The offset is deliberate: a naive isoformat() (no zone) is ambiguous, and a
|
|
61
|
+
client parsing it (JS `new Date(...)`) reads it as *browser-local*, so a
|
|
62
|
+
timestamp stamped on a UTC server renders shifted by the viewer's UTC
|
|
63
|
+
offset. Stamping the zone makes every stored timestamp (creation, modified,
|
|
64
|
+
occurred_at, …) an unambiguous instant that round-trips correctly."""
|
|
65
|
+
return datetime.now(timezone.utc).isoformat()
|
|
59
66
|
|
|
60
67
|
def add_days(dt, days):
|
|
61
68
|
"""Add days to a date."""
|
|
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
|